0

I am teaching myself x86 assembly without the use of a course or structured program. Because of this, I am probably missing something simple and fundamental, but I have yet to find a solution to this as I cannot think of what to Google anymore.

I am trying to expand on the simple "Hello, World!" program by using functions (procedures?) to be able to eventually process user input and print it to the screen. I thought I have a good understanding of the stack, stack pointer, and the opcodes I have used, but evidently, I do not.

Here is my code:

    [BITS 32]

    section .data

        helloWorld db "Hello, World!",0x0a

    section .text
    global _start

    exit:

        mov eax,1 ; exit
        mov ebx,0 ; Return 0
        int 80h ; Call kernel

    strLen: ; String length is in ecx by the end

        push edi ; Save edi
        push ebp ; Save old base pointer
        xor ecx,ecx ; Clear ecx
        mov edi,[ebp + 8] ; Skip over return address to get to string
                            parameter
        not ecx ; Sets ecx to highest value (4,294,967,295 or -1)
        sub al,al ; Clear al
        cld ; Clear direction flag
        repne scasb ; Scans string starting at address in edi for NUL
                    ; As it scans, decrements ecx and increments edi
        not ecx ; ecx contains string length plus NUL
        dec ecx ; Get rid of terminating NUL
        pop ebp ; Restore base pointer
        pop edi ; Restore edi
        ret ; Return to location where called

    printStr:

        push ebp ; Save old base pointer
        mov ebp,esp ; Save stack pointer as base pointer
        ; ebp + 0 == old ebp
        ; ebp + 4 == return address
        ; ebp + 8 == parameter
        mov eax,4
        mov ebx,1
        mov edx,[ebp + 8] ; Move string parameter to edx
        push edx ; String parameter for strLen
        call strLen ; Find string length
        mov edx,ecx ; Move string length to edx
        mov ecx,[ebp + 8] ; String to be printed is parameter
        int 80h ; Call kernel

        pop ebp ; Restore old base pointer
        ret ; Return to location where called

    _start:

        push helloWorld
        call printStr
        call exit

I think my problem is somewhere with the [ebp + 8] to obtain the first parameter for the functions, but I am not sure why this would be an error. I'll admit I do not really know why I am using the brackets, and I have not found an explanation yet. I have tried many different combinations to remedy this, including trying to move the address there to a different register. I have gotten "Segmentation Fault (core dumped)" with NASM and ld on the Ubuntu subsystem within Windows 10 with an i5-6600k and "Invalid memory reference (SIGSEGV)" on rextester.com, which I use when I do not have access to Linux.

  • 2
    I count 4 `push` but only 3 `pop`. That doesn't seem right. – melpomene Feb 26 '18 at 04:11
  • 3
    Your `strLen` is looking for NULL - your input string is not NULL terminated as its expecting. I would suggest attaching a debugger and stepping through your code. This is 1000000 times more important as a skill in assembly programming. – Simon Whitehead Feb 26 '18 at 04:12
  • 1
    Also, `strlen` trashes `al` and therefore the `eax` value of 4. – 500 - Internal Server Error Feb 26 '18 at 04:13
  • 1
    `sub al,al` is strictly worse than `xor eax,eax` as a way to set `al` to zero. https://stackoverflow.com/questions/45660139/how-exactly-do-partial-registers-on-haswell-skylake-perform-writing-al-seems-to and https://stackoverflow.com/questions/33666617/what-is-the-best-way-to-set-a-register-to-zero-in-x86-assembly-xor-mov-or-and. Always xor-zero the full register unless you specifically *want* to leave the upper bytes unzeroed. – Peter Cordes Feb 26 '18 at 04:16
  • 5
    [The Windows Ubuntu subsystem only supports 64-bit executables, and only the 64-bit native `syscall` ABI, not `int 0x80` even in 64-bit executables](https://stackoverflow.com/questions/47736104/assembly-compiled-executable-on-bash-on-ubuntu-on-windows-doesnt-produce-output). This isn't strictly a duplicate because your other bugs probably cause a crash first, but **use a debugger**. – Peter Cordes Feb 26 '18 at 04:21
  • @SimonWhitehead I thought the 0x0a (10) at the end of the string is the NULL terminator? I agree with your sentiment about debugging, but I have not figured GDB out yet. – Jace Dukes Feb 26 '18 at 04:23
  • There are _many_ issues with your code. A quick eyeball suggests you're also not restoring your stack pointer in your prologues. – Simon Whitehead Feb 26 '18 at 04:24
  • 1
    10 is newline / line feed. 0 would be NUL. – melpomene Feb 26 '18 at 04:25
  • @PeterCordes Firstly, thank you for those questions. Secondly, I figured that out after installing what was required to run it in a proper environment but was still unable to run the executable, so I compiled it as a 64-bit executable with 32-bit code as I thought this should be fine due to backwards compatibility. Is this incorrect? – Jace Dukes Feb 26 '18 at 04:27
  • @melpomene Oh. Thanks. – Jace Dukes Feb 26 '18 at 04:28
  • Also, how did you assemble and link? You used `BITS 32`, which means you *might* have assembled 32-bit machine code into a 64-bit executable, so the CPU would decode it as if it was 64-bit code (You might possibly not crash just from that, but `push` in 64-bit mode pushes 8 bytes. e.g. what you wrote as `mov edx,[ebp + 8]` would decode as `mov edx,[rbp + 8]`, and load the return address instead of what you `push`ed before the `call`.) – Peter Cordes Feb 26 '18 at 04:28
  • @SimonWhitehead What do you mean by prologues? – Jace Dukes Feb 26 '18 at 04:28
  • @PeterCordes I assembled using nasm -f elf64 debug.s and linked by ld -o debug debug.o as I recognized I could only use 64-bit. This finally allowed me to execute. About the other information, I didn't think of that. So could I quickfix by doing [ebp + 16] instead? Or should I move to a proper 32-bit environment? – Jace Dukes Feb 26 '18 at 04:30
  • 2
    Ok, that explains how you got it to execute. But `BITS 32` doesn't switch the CPU into 32-bit compat mode, it just tells the assembler how to translate source into bytes. Putting 32-bit machine code in a 64-bit executable can't help you, and is yet another problem vs. assembling your code as 64-bit. But `int 0x80` always crash under the Windows Ubuntu subsystem, so it doesn't matter. You need to either fully port to 64-bit or use Linux (e.g. in a virtual machine). – Peter Cordes Feb 26 '18 at 04:31
  • @PeterCordes Okay thanks. I will move to Linux in a VM then. – Jace Dukes Feb 26 '18 at 04:32
  • Apologies @JaceDukes I meant the epilogue. Where you `pop ebp` in your `printStr` routine you should also be restoring your stack pointer. There is a `leave` instruction you can use here (which has an `enter` equivalent in the prologue but IIRC `enter` is slower than the `push ebp`, `mov ebp,esp` pair). Whilst you're not allocating local stack space in this function it will still restore the stack pointer when you're `push`'ing arguments to other functions. – Simon Whitehead Feb 26 '18 at 04:37
  • Its not strictly a requirement though... just something I think is a good habit for while you're learning and then when you're able to self-optimize this stuff by holding the stack pointer in your brain .. then you can begin to forget about it. – Simon Whitehead Feb 26 '18 at 04:41
  • @SimonWhitehead No worries. I was not aware of that, but it makes sense. So would `mov esp,ebp`, `pop ebp` be faster than `leave` if `push ebp`,`mov ebp,esp` is faster than `enter`? I'm all for optimizing on the fly and understanding what I'm writing better, even if it's not a requirement. – Jace Dukes Feb 26 '18 at 04:44
  • @JaceDukes: Fastest would be to ignore C calling conventions (e.g. pass everything in registers where possible) and not bother with a frame pointer at all (e.g. use things like `mov eax,[esp+4]` instead if you need to access anything on the stack, which you mostly won't need to do anyway); but also to "inline" everything to get rid of the function calls, and to get the assembler to determine the length of the string instead of calculating it at run-time (e.g. `mov edx,[helloWorld.end - helloWorld] ;edx = length of string`). With these changes the code can be reduced to about 8 instructions. – Brendan Feb 26 '18 at 05:01
  • `strLen` does not set up `ebp` (but by accident when called from `printStr` it would find at theirs `[ebp+8]` the string pointer too, so you wouldn't catch this problem just by running it, if everything else would work). – Ped7g Feb 26 '18 at 08:28

0 Answers0