5

So I'm learning x86 Linux assembly with NASM syntax (Oh god, not this again, you're all thinking). I'm trying to make a subroutine that will simply print the value in EAX to stdout. The code runs and exits without error, but nothing prints. I can't figure out why. First of all, here is the file I'm working in:

segment .bss
    to_print:   resd 1

segment .text
    global print_eax_val

print_eax_val:                  ;       (top)
    push    dword ebx           ;Stack:  edx
    push    dword ecx           ;        ecx
    push    dword edx           ;        ebx
                                ;       (bot)

    mov     ecx,eax             ;ecx = eax

    mov     [to_print],ecx      ;to_print = ecx

    mov     eax, 4              ;sys_write
    mov     ebx, 1              ;to stdout
    add     ecx, 47             ;add 47 for ASCII numbers
    mov     edx, 2              ;double word = 2 bytes
    int     0x80

    mov     eax, [to_print]     ;eax = original val
    pop     edx                 ;pop the registers back from the stack
    pop     ecx
    pop     ebx                 ;Stack: empty

    ret

This is called from my main file, which looks like this (this is probably irrelevant, unless I'm missing something drastic).

segment .data
        hello   db      "Hello world!", 0
        newline db      0xA
        len     equ $ - hello
        len2    equ $ - newline

segment .text
        extern print_nl
        extern print_eax_val
        global main

main:
        enter   0,0

        call    print_nl

        mov     eax, 1

        call    print_eax_val

        mov     ebx, 0          ;exit code = 0 (normal)
        mov     eax, 1          ;exit command
        int     0x80            ;ask kernel to quit

print_nl is just another subroutine that defines and prints a newline. This runs successfully and prints a new line as expected.

Does the problem have to do with the length parameter for my sys_write call? I'm giving it 2, which is the size of a dword, which is the size of both the EAX register and my to_print label, which I reserved with resd 1. I tried changing the length to 1, 4, 8, 16, and 32 out of desperation... Nothing worked.

EDIT: For anyone who is wondering, here is how I fixed the code: (I will put asterisks on lines that I changed):

segment .bss
    to_print:   resd 1

segment .text
        global print_eax_val

print_eax_val:                      ;       (top)
        push    dword ebx           ;Stack:  edx
        push    dword ecx           ;        ecx
        push    dword edx           ;        ebx
                                    ;       (bot)

        mov     ecx,eax             ;ecx = eax

        mov     [to_print],ecx        ;to_print = ecx

****    add     dword [to_print], 48

        mov     eax, 4              ;sys_write
        mov     ebx, 1              ;to stdout
****    mov     ecx, to_print
        mov     edx, 2
        int     0x80

****    sub     dword [to_print], 48
        mov     eax, [to_print]     ;eax = original val
        pop     edx                 ;pop the registers back from the stack
        pop     ecx
        pop     ebx                 ;Stack: empty

        ret

Basically, ecx must contain the address of the block you want to print, NOT the value itself. As is pointed out in the selected answer, this will only work if eax is in the range 0-9.

EDIT 2: So I was a little bit confused about the 2nd parameter for sys_write (the one stored in edx). I think it just refers to a number of bytes. So for a dword, as I was using, it would be proper to use 4 there, because a double word is 4 bytes, or 32 bits. I'm guessing it worked because x86 is little-endian. So in memory, the hex value of to_print would look like this:

90 00 00 00

And with a supplied length of two, sys_write gets:

90 00

So the value luckily doesn't get corrupted.

I later changed the code to store to_print as a byte instead, using resb 1 and accessing it using byte instead of dword... A byte is fine here, because I know I'm not going to give to_print a value above 9.

Mortimer McMire
  • 330
  • 2
  • 5
  • 16

2 Answers2

4

I'm very rusty at assembler, but it looks like ECX contains the value 48, when it should contain the address of the buffer to be written.

I presume what you intended in print_eax_val is to take the binary value of EAX, add the ASCII offset to digit 0 (which should have been 48, not 47) and then print that single character. To do this, add 48 before storing the value in to_print, put the address of to_print in ECX, and set the length (EDX) to 1, because you're writing only one character.

Now remember that this will work for EAX values between 0x0000 and 0x0009 only. When you go past 9 you will get other ASCII characters.

Explaining how to take an arbitrary binary value of EAX and convert it to a decimal string that can be printed is far beyond the scope of SO.

Jim Garrison
  • 85,615
  • 20
  • 155
  • 190
  • Thanks very much! Your answer was very helpful. Yeah, I realize this will only work for 0 through 9, I'm just trying to get some of the basics down. Maybe I'll start with some array/pointer int-to-string business eventually. I'll edit my post with the updated code, for anyone else who wants to know. – Mortimer McMire Jul 02 '13 at 22:47
  • 3
    I applaud you for tackling assembly language and wish you luck. BTW, your question is a prototypical good SO question. You gave exactly the information needed, showed what you had tried, and asked a specific question. +1 – Jim Garrison Jul 02 '13 at 22:54
  • 1
    Thanks again, and I would certainly classify your answer as a "prototypical good SO" answer. I decided to learn some x86 assembly after a gigantic C++ segfault/pointer meltdown in a project I'm working on. First of all, it is a nice diversion, second, I want to know what I'm *actually* doing when writing code. – Mortimer McMire Jul 02 '13 at 22:59
3

The write system call takes a buffer of characters to print, pointed at by %ecx with a length given by %edx. A register like %eax on the other hand contains a 32-bit integer.

So if you really want to print %eax, you need to first convert that integer to a sequence of characters. You could covert it to ASCII decimal, or hexadecimal, or any other base you like, but you need to make it into characters.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • Thanks for your help! Your answer was entirely helpful (the internet is actually surprisingly sparse on assembly language q&a, or maybe I just don't know where to look). I got to the other answer first, but if I could select two correct answers, I would. – Mortimer McMire Jul 02 '13 at 22:49
  • Hah, no mistake is "minor" in assembly (from what I have learned so far). I was confused about what I was supposed to put in that length parameter. See Edit 2 in my post for more explanation. But thanks! – Mortimer McMire Jul 03 '13 at 12:22