From: Frank K. <fbk...@zy...> - 2008-10-22 09:07:22
|
Tyler Littlefield wrote: > hello, > thanks a lot for the help, and sorry for the delayed reply. No rush... > I've got a couple questions, though. > I kinda wanted to just work out the length rather than having a ton of > constants declared. Especially when I start writing code that requires > that the program be small enough, such as a boot loader, etc. > Can you suggest a more efficient way of doing things? Declaring constants won't add anything to the size of the code. You not only save the "logic" for finding the zero - you save the zero! Compare: msg db "Hello world", 10 msg_len equ $ - msg ... mov ecx, msg mov edx, msg_len ... With: msg db "Hello world!", 10, 0 ... mov ecx, msg xor edx, edx ; get length in edx .getlen: cmp byte [ecx + edx], 0 jz .gotlen inc edx jmp .getlen .gotlen: ... > I'd also thought it might be needed when reading user data, or printing > other strings. Reading user data (with sys_read) returns the number of bytes read, but those "other strings" are likely to be zero-terminated. You can "just call strlen", of course. That'll require linking in code for the dynamic loader. It'll return the length in eax... I think I like the "inline" way better... > I'm relatively new to this, so have some idea what you just did, but you > lost me with your esp. :) Maybe I "got ahead of myself". :) > Also, was there a reason for the 'nop' instruction? Yeah... sort of. It serves as a "parking place for gdb". Gdb is happier stepping through your code from the beginning if there's a one-byte instruction first. Doesn't really need to be there. Lemme try this "esp thing" with a few more comments... I showed you a thing that used the stack as a "buffer" to print a single character, and you said... >>> So, I would send "hello world." to the stack, then pop it off in the >>> write func and use the strlen for edx. You could copy the whole "hello world" string to the stack and print it from there, but the "usual" way would be to pass the address of the string on the stack. But you can't really "pop" it off in the function... the "call" instruction uses the stack to store the return address... >> global _start >> >> section .data >> msg db "Hello world!", 10, 0 >> >> section .text >> _start: >> nop >> push msg Since the stack starts at a high memory address and works down, this subtracts 4 from esp, and puts the address of "msg" in memory at [ss:esp] (ss is the default segment for esp... and ebp... so you wouldn't write it in your code, but it's still part of the address). >> call print_zero_terminated_string This subtracts another 4 from esp, and puts the return address (the address of the "add" instruction) at [ss:esp]... but the "ret" removes it, and adds the 4 back on. But we've still got the parameter on the stack, so... >> add esp, 4 We could have "pop"ped a dummy register, too. In fact, since we're just exiting, we didn't really need to clean up the stack at all... but it's a good idea... >> ... >> mov eax, 1 >> int 80h >> >> print_zero_terminated_string: When we get here, the return address is at [esp], the first parameter is at [esp + 4] (if we had more parameters, they'd be at [esp + 8], [esp + 12], etc.). It is also common to do: push ebp ; save caller's ebp mov ebp, esp ; use ebp for a "stack frame pointer" ... Now the first parameter is at [ebp + 8], we can subtract something from esp to make a place for temporary variables, and the parameters and local variables are at constant offsets from ebp, no matter what we do with esp... and before the "ret" we need to: mov esp, ebp pop ebp ... There are advantages to that, but we didn't do it that way, so... >> mov ecx, [esp + 4] ; buffer in ecx >> xor edx, edx ; get length in edx >> .getlen: >> cmp byte [ecx + edx], 0 >> jz .gotlen >> inc edx >> jmp .getlen >> .gotlen: >> mov ebx, 1 ; stdout >> mov eax, 4 ; __NR_write >> int 80h When we get to the "ret", esp had *better* be pointed to the return address! >> ret Hope that makes the esp part clearer... Best, Frank |