Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I am befuddled by the idea of saving the return address explicitly (mov rdi, $ + 15) while also using call/ret. Maybe there is something clever going on here, but I don't understand the purpose.

calculateStrLength is not only calculating the string length; it's also pushing the contents of the string onto the stack (and expanding each byte to 8 bytes, because that's the only valid size of push on x64). The stack pointer is no longer pointing to the return address, so the code puts the return address (which was calculated by hand by looking at the dissasembly and counting bytes rather than using a label for some reason, but I digress) back onto the stack and then using RET to pop it right back off and jump to it (PUSH RDI + RET is equivalent to JMP RDI).

Then reverseStr loops over the string length, popping a character off the stack one at a time and writing all eight bytes into the output buffer (running off the end of the reserved buffer for the last 7 characters, incidentally).

I suppose this is an interesting exercise to understand how the stack and call/ret mechanisms work, but it's not the way to write understandable code. Using the stack in this way will almost certainly also break the return address prediction that modern CPUs perform, making the code slower than the straightforward version that doesn't use the stack at all.



Yes, the text is definitely not what interested readers should actually try to achieve, even if it motivated them to learn more.

I don't know about Linux 64-bit ABI limitations, but if you want the interoperable code in Win64, you should actually learn to not use push except in prologs, epilogs and leaf functions and even then the trickier part is to maintain the exception info consistent. So some other approaches are better to learn first.

drv, I also agree, it's highly inefficient making memory pressure n times 8 bytes on the new stack instead of simply accessing the bytes as they are, keeping the caches doing their job.


It won't break return address prediction, as long as the call/returns are matched prediction will still work.

Its a symptom of doing really ugly things with the stack


> Its a symptom of doing really ugly things with the stack

Yes, that's spaghetti code. In general I strongly recommended to write assembly functions which follow the platform's ABI (I'm a TA for a course using assembly programming). At the very least, function calls and returning values should be consistent and function calls shouldn't have this kind of side effects. In this case

* calculateStrLength effectively leaks stack space because it pushes the input string and doesn't balance the stack before returning - this is why the return value is passed using rdi. At least the return value pushed by call could have been preserved in calculateStrLength itself. It also increments the size counter without resetting it (it's reset to 0 in _start) which is a bit of a weird choice.

* then reverseStr (which is not a function but just a label in _start) pops the string off the stack and copies it to the OUTPUT buffer (but doesn't copy an end of string NULL character - which works because the buffer is in the bss section, but it's not a good practice). The return address pushed by the call to calculateStrLength is left on the stack.

The whole thing is designed pretty weirdly because reversing a string is trivial to do in place (or with just an input and output buffer), there's no need to use the stack.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: