Author Topic: how to use stack?  (Read 16424 times)

Offline new_geek

  • Jr. Member
  • *
  • Posts: 11
  • Country: 00
how to use stack?
« on: November 06, 2013, 01:35:52 AM »
Hello again,
this time before doing the assignment I'd like to know how we can implement stack. the code below, I learned it from the text box, and frank. but there is a problem in it. when I compiled those line, it shows segmentation fault right at "push 1"

Quote
Code: [Select]
section .text
global _start

_start:
nop

; TODO: your code here
;clearing the registers that are going to be used
;to point to the return address
;and the top of the stack
xor eax,eax ;to hold temporaryly the value of num1 arrays
xor ebx,ebx ;to hold temporaryly the value of num2 arrays
xor ebp,ebp ;ebp for pointing to the return address
xor esi,esi ;index for the arrays
xor esp,esp ;index for the stack level
initialProg:
PUSH 1
PUSH 2
call sub_Compare
add esp, 2
mov cl, al
inc esi
loop initialProg

sub_Compare:
PUSH ebp
mov ebp, esp
mov al, [ebp+3]
cmp al, [ebp+6] ; comparing the value to get the bigger value
ja .alIsBigger
mov al, [ebp+6]
.alIsBigger:
mov ecx, eax
jmp .subDone
.blIsBigger:
mov dl, bl
.subDone:
mov esp,ebp
POP ebp
RET

sincerely,
--geek

Offline Gunner

  • Jr. Member
  • *
  • Posts: 74
  • Country: us
    • Gunners Software
Re: how to use stack?
« Reply #1 on: November 06, 2013, 03:14:52 AM »
Code: [Select]
xor esp,esp
NOOOOOOOOO!!!!  NEVER, EVER, touch esp or ebp like this, you just destroyed the stack address!  You can (once you know what you are doing) use ebp as a spare register, but you should never modify esp like this!!!

Code: [Select]
mov al, [ebp+3]
cmp al, [ebp+6] ; comparing the value to get the bigger value

Values on the stack are 4 bytes, so by creating a stack frame, the params are at [ebp + 8] and [ebp + 12]

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: how to use stack?
« Reply #2 on: November 06, 2013, 04:12:34 AM »
Hi again,

Apparently I wasn't very clear in our private messages!

Code: [Select]
xor esp,esp ;index for the stack level

In particular, I certainly didn't intend to tell you to do that! Get rid of that line, to start with. There's plenty more wrong with it, but that should get you past "push 1" at least.

I understand you're using "ddd" to step through your code. I've never used "ddd" but I think I know what it is. If that's working for you, it should help a lot. I understand your instructor suggested you keep an eye on EIP and watch how it changes as your subroutine is called, and returns. I'll suggest you also keep an eye on esp. In particular, watch how it changes when you do "push 1". You are not pushing a single byte onto the stack. The 1 is stored in memory as a single byte, but it's sign-extended to a dword (4 bytes, 32 bits) and that's pushed onto the stack. So to "clean up" after your subroutine returns, you need to "add esp, 8" not 2. This means that your offsets from ebp in your subroutine are wrong, too. After the usual prolog, the "first" (last pushed) parameter is at [ebp + 8]. Stack should look like...

parameter N
parameter N -1
...
...
parameter 0
return address
caller's ebp

At this point, ebp is "locked" to the present value of esp - and we do NOT want to alter ebp! Passed parameters are ar [ebp + 8], [ebp + 12], [ebp + 16], etc. and local variables (when we get to them) are at [ebp -  4], etc. That's how ebp is usually used (although it can be used as a general purpose register). In this case, your caller wasn't using ebp, but often the caller will be using it, so we don't want to trash it.

You've also got a problem with using the "loop" instruction (I think I showed you that). "loop" uses ecx as its loop counter. You modify ecx (cl) both in your loop and in your subroutine. This means it's going to loop forever! I probably shouldn't have showed you using "loop". Although the subroutine I showed you did not alter ecx, it is "allowed" for a subroutine to trash ecx according to most calling conventions. Using "loop" (and thus ecx) and calling printf in the middle of it is a common newbie problem. The solution is to preserve ecx around subroutine calls (ugly) or don't use the "loop" instruction. You can do the same thing using any other register (not esp, please!)...
Code: [Select]
mov edi, 4
top:
; do something
dec edi
jnz top
; at this point, your loop is finished
; do something else!
mov eax, 1 ; sys_sxit
xor ebx, ebx ; claim "no error"
int 80h
; at this point, you don't have to do anything else.
; good place for your subroutines

Well, I see that Gunner has made some suggestions. Thanks, Gunner! Take another shot at this and get back to us...

Best,
Frank


Offline 0xFF

  • Jr. Member
  • *
  • Posts: 15
Re: how to use stack?
« Reply #3 on: November 06, 2013, 02:35:28 PM »
Hello again,
this time before doing the assignment I'd like to know how we can implement stack. the code below, I learned it from the text box, and frank. but there is a problem in it. when I compiled those line, it shows segmentation fault right at "push 1"

-snip-

sincerely,
--geek

Hey geek,

I'm gonna be brutal and encourage you to have a good sense of humor about this, but that is a horrible pile of code that just... many things wrong with it. I think you should save that somewhere safe and look back on it in a couple of weeks time when you're coding this stuff with confidence (because you will, it'll fall into place, keep working at it), because you will really enjoy that moment and pat yourself on the back for a job well done. I think we all have an attempt like this we produced when starting out, and its great to reflect on lessons learned... that said I'm just going to focus on one critical concept that's good to learn.

As frank has already alluded to, when you give the instruction "push 1" it's actually putting a 4 byte DWORD with the value of 1 onto the stack. Now the size of the data you move around with other instructions like mov or cmp etc are all very flexible, from bytes all the way up past QWORDS (8 bytes, 64 bits) and so on. But, the two classic stack operations "push" and "pop" are basically limited to the size of your cpu. I think of it as like a stamp or a printing press, it stamps down in a fixed width, inflexibly, the size of a register, or picks up a unit the size of a register. You're using 32 bit code (4 bytes) so push and pop move data the size of DWORDs. I tend to code in 64 bit asm, my cpu's native size, so my push and pop instructions 'stamp' with a QWORD size, 8 bytes.

Another tip: I'm not sure if xor is a faster instruction than mov, but the xor of registers against themselves is a little obtuse, I always feel that mov eax, 0 style of operations, at the time said operation is being set up is a simpler one to follow.

But, the sysseg you're getting is because you clear esp, esp should point to the last item pushed onto the stack. By clearing it, you then try to push onto the absolute lowest address in your system, which is guaranteed to be owned by the kernel, and the kernel will slap you for trying to touch its turf. It's saying you can't put data there, use the stack I gave you. :P

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: how to use stack?
« Reply #4 on: November 06, 2013, 06:39:18 PM »
Quote
Another tip: I'm not sure if xor is a faster instruction than mov, but the xor of registers against themselves is a little obtuse, I always feel that mov eax, 0 style of operations, at the time said operation is being set up is a simpler one to follow.

When I see "mov eax, 0" I wonder "Why is he preserving flags?"

Back in the "good old days", xor was both shorter and faster. I suppose I should have said "mov ax, 0" since eax didn't exist at that time. These days, I don't think there's any real difference in speed. I suspect either causes a register rename. Still shorter! You're probably right that "mov reg, 0" is easier to follow. Either works.

Best,
Frank


Offline 0xFF

  • Jr. Member
  • *
  • Posts: 15
Re: how to use stack?
« Reply #5 on: November 08, 2013, 12:33:09 PM »
Quote
Another tip: I'm not sure if xor is a faster instruction than mov, but the xor of registers against themselves is a little obtuse, I always feel that mov eax, 0 style of operations, at the time said operation is being set up is a simpler one to follow.

When I see "mov eax, 0" I wonder "Why is he preserving flags?"

Back in the "good old days", xor was both shorter and faster. I suppose I should have said "mov ax, 0" since eax didn't exist at that time. These days, I don't think there's any real difference in speed. I suspect either causes a register rename. Still shorter! You're probably right that "mov reg, 0" is easier to follow. Either works.

Best,
Frank

Hey Frank... I figured you'd be right about that, and did a little research. There's loads of crap out there, but this guy has done some good work and offers his optomisation related data, number 4 of which is data on opcode speed for a comprehensive list of processors.

http://www.agner.org/optimize/#manuals

Looks like for most modern processors since pentium a mov operation from immediate values or a register, into another register takes one cpu cycle. XOR looks the same, though they both get slop when you're referencing memory. That's impressive! Just reinforces the joy of x64 asm: keep it in all those juicy registers and all your work takes teensy time to run. :D

Good resource that one, I downloaded all his files for reference.


Offline Rob Neff

  • Forum Moderator
  • Full Member
  • *****
  • Posts: 429
  • Country: us
Re: how to use stack?
« Reply #6 on: November 08, 2013, 05:15:33 PM »
+1 to the agner reference.

Offline new_geek

  • Jr. Member
  • *
  • Posts: 11
  • Country: 00
Re: how to use stack?
« Reply #7 on: November 12, 2013, 05:21:55 AM »
Hi guys, thank you for the feedback, I learn a lot. I really appreciate it.
Here is the chunk of the code. which I think runs nicely... hopefully.
I tested it using ddd.

Quote
Code: [Select]
; code by : new_geek
; #studentIDnumberCensoredLoL
; Comparing each pair of numbers using stack and passing them as arguments
; to the subroutine store the maximum value in [MAX]
; without using any CPU registers except
; EBP register to access the parameters from the stack.

section .data
num1: db 1, 2, 3, 4
num2: db 5, 1, 7, 2

section .bss
MAX resb 4

section .text
global _start

_start:
nop

; TODO: your code here
;clearing the registers that are going to be used
;to point to the return address
;and the top of the stack
xor eax,eax ;to hold temporaryly the value of num1 arrays
xor ebx,ebx ;to hold temporaryly the value of num2 arrays
;;ebp for pointing to the address
;(return and current stack)
xor esi,esi ;index for the arrays
mov ecx, 4
xor edx,edx; to hold temporaryly biggest number
;;esp as index to point stack level

initialProg:
mov al, [num1+esi]
mov bl, [num2+esi]
push eax
push ebx
call sub_Compare
add esp, 8
mov [MAX+esi], dl
inc esi
cmp esi, ecx
je done
loop initialProg
sub_Compare:
push ebp
mov ebp, esp
mov dl, [ebp+8]
cmp dl, [ebp+12] ; comparing the value to get the bigger value
ja .alIsBigger
je .subDone
.alIsBigger:
mov [MAX], dl
jmp .subDone
.blIsBigger:
mov dl, bl
.subDone:
mov esp,ebp

pop ebp
RET

done:
mov ebx,0
mov ebx,eax
mov eax, 1
int 0x80

and I'm sorry if I've caused misunderstanding, frank, what I meant by I learned the code from you is that the way how to use esp and ebp. and that clearing those two registers was my own idea. because from reading your message and the text books I know that ESP and EBP are special case registers, for us to access the stack. but I didn't know that it is not supposed to be cleared  :-[ but now I know that leaving those two as it is, is the best.

to 0xFF: lessons accepted, I put that code in separate folder :D and keep going. then I'll take some time to take a look at it again. Oh I have a good sense of humor too ;D

Offline 0xFF

  • Jr. Member
  • *
  • Posts: 15
Re: how to use stack?
« Reply #8 on: November 12, 2013, 01:49:21 PM »
Good lad Geek. I'd have to say though that the revised code you've posted seems kind of confusing. Is this an assignment you've been given from formal study, or a test project you wrote to learn about the stack. I really think that it would help to clarify that. If this is an assignment I think the instructions in the comments are whacky, but if its your own challenge to yourself, I'd definitely like to suggest some other ways of achieving this functionality.

But more than anything, please, please, take this in: comment more. Comment! I comment every single line of code, even the obvious ones (check my cat64 thread) and I find it just the most liberating and useful thing. It serves two purposes: 1. it helps you as the programmer remember what you're doing and why, and the same for someone auditing your source code. 2. More importantly, I think, it helps you when you're writing the code, because you have to stop and trace the logic of what you're doing; you say "comparing x to y to check if x is a negative number",you have to think that through, and often your mistakes become immediately obvious, you might say  for example realise you need to use "jl" next line rather than "jb", because jb is for unsigned numbers.