0

I was tinkering around a bit with putchar, push and pop. When I tried to push the value of rcx, do something with it, call putchar and pop it back into rcx, I found that the value in rcx was changed to 0. Like in the first bit of code.

For comparison, I made the second bit of code, where I push rcx, do something with it that is not putchar and pop it back into rcx, rcx still is at the value it was pushed at.

mov rcx, 123
push rcx
inc rcx
call [putchar]
pop rcx
call [putchar]


mov rcx, 123
push rcx
inc rcx
pop r12
call [putchar]
mov rcx, r12
call [putchar]

Does putchar actually clear the stack? Does anyone know how to protect the stack (or at least the important part of it) from this?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
S.Klumpers
  • 410
  • 3
  • 14
  • I would suspect that you are failing to take the proper calling requirements for `putchar` into account. Which platform are you on? Which architecture? On that architecture, what does the ABI state for calling conventions? What is the actual definition of `putchar`? – David Hoelzer Feb 10 '16 at 16:25
  • I'm using Windows, x64 architecture, the first (int) parameter should go in rcx. The definition of `putchar` is `int putchar(int c);`. https://msdn.microsoft.com/en-us/library/48szs1c2.aspx – S.Klumpers Feb 10 '16 at 16:37
  • The `int` is returned to `rax` and is equal to the value of `rcx` before calling `putchar`, so the stack isn't really needed for saving the value of `rcx`. Still I would like to know how to preserve the stack. – S.Klumpers Feb 10 '16 at 16:39
  • 4
    Looks like you aren't allocating 32-bytes on the stack for the register parameter area before you make the call to `putchar`. As well you should make sure the stack is 16-bytes aligned before making any call. See: https://msdn.microsoft.com/en-us/library/ew5tede7.aspx . The callee (the function that is called) is allowed to modify those 32-bytes on the stack all it wants. In your code the value in `rcx` you push happens to become part of the register parameter area so can be overwritten. – Michael Petch Feb 10 '16 at 16:50
  • Uh, can you please explain how I'm supposed to allocate these bytes and how to align the stack? I tried a few things like adding some number to `rsp` and substracting it after, `and`ing `rsp` with 16. – S.Klumpers Feb 10 '16 at 17:18
  • You'd have to subtract from `rsp` to allocate space. And although not the best method of doing alignment, if you were to use `and` on `rsp` you'd have to use the value `-16` not `16`. – Michael Petch Feb 10 '16 at 17:27
  • Okay, thank you very much! – S.Klumpers Feb 10 '16 at 17:34
  • Substracting 32 from `rsp` works, but values of 24 and above also seem to work. Is moving the pointer by 24 (24 whats? Bytes?) always enough or does that differ between functions? – S.Klumpers Feb 10 '16 at 17:48
  • Just put the values in the correct register, then call the function. You do not have to push the values onto the stack! – Gunner Feb 10 '16 at 18:56
  • @S.Klumpers: Your experiment shows that 24 is enough for *that* implementation of *that* function. The ABI says that the called function is allowed to clobber 32B of shadow space above the stack, so reserve that much unless you're doing brittle inter-procedural optimizations and also not saving/restoring registers untouched by your callee. And yes, all modern CPU architectures including x86 have byte-granularity addresses. So 24 is 24B... And don't actually `and rsp, -16`: assume that the stack was 16B-aligned before your caller called you. – Peter Cordes Feb 10 '16 at 19:01
  • @Peter Cordes: What do you mean, that I should not use `and rsp, -16` before I call the function, or that I should not use it in my own functions? By the way, what is this alignment good for? When I push something to the stack and don't align it, but just move the pointer by 32B it works just fine. Using `and rsp, -16` ruins the pushed data when more than one thing was pushed. – S.Klumpers Feb 10 '16 at 19:53
  • The compiler can assume the stack is 16B-aligned, and use SSE aligned stores, because the ABI requires it. This is why they chose to require it when designing the ABI. https://software.intel.com/en-us/forums/intel-isa-extensions/topic/291241. See http://stackoverflow.com/questions/19128291/stack-alignment-in-x64-assembly for an example of a function that reserves some space and makes a `call`. – Peter Cordes Feb 10 '16 at 20:22
  • 1
    @PeterCordes : What we don;t know is the context of the function. If for example there is a typical stackframe with no other values pushed, then the you need no further alignment. If there was no stackframe (RBP not pushed) then the stack would need realigning by subtracting 8 from RSP (plus any local stack space requirement that is evenly divisible by 16). Now that doesn't take into account pushing another value like `rcx` later on. Just referring to alignment that typically occurs when allocating space for the local variables and scratch space where realignment is usually done. – Michael Petch Feb 11 '16 at 00:54
  • I'm actually still wondering, why don't the callees move the stack pointer over by 32B themselves instead of using stack space that may or may not be already in use? – S.Klumpers Feb 11 '16 at 19:51
  • The caller does it and not the callee simply because that's what the ABI says. As for an educated guess on why it was decided like that: if a function calls multiple other functions, it only has to allocate this parameter area once, whereas if it were the callee's responsibility, it would have to be done on each function call. – ThFabba Feb 12 '16 at 08:06

0 Answers0