Output Data Register value in NASM

2020-06-23 03:53发布

问题:

new guy here, and already I have a question.

I'm adapting example code used in Jeff Duntemann's assembly books, and I want to print out an integer value stored in a data register to terminal?

What the following code below does is that it prints out the strings okay, pushes value in ECX okay, but when it gets to the following:

                pop ecx
                mov eax,4                          
                mov ebx,1                          
                mov edx, ecx

                int 80h  

It doesn't display the contents of edx on the terminal, though I think I told it to with mov eax,4 etc.

Anyone able to give me any "pointers" (pun intended)?

Code for reference (amended as of 17/06/2012) :

SECTION .data
    submessage: db "I am subtracting 5 from 10!", 10
    msglen: equ $-submessage

    ansmsg: db "Answer is:", 10
    msglen2: equ $-ansmsg
    EOL: db 10

SECTION .bss
    msg: resd 2                                         ; reserve space for 2 dwords

SECTION .text

global _start

_start: nop

;Displays test on shell
                    mov eax,4                           ;print to terminal     
                    mov ebx,1                                      
                    mov ecx, submessage
                    mov edx, msglen
                    int 80h                             ;"I am subtracting 5 from 10!"

                    mov eax,4                           ;print to terminal   
                    mov ebx,1                                            
                    mov ecx, ansmsg
                    mov edx, msglen2
                    int 80h                             ;"Answer is..."

;Subtraction operation below:                  
                            mov edx, 10
                            sub edx, 5
                            mov [msg], edx                ; store 5 in msg


; now we need to print  msg to terminal                            


                            mov eax, 4                  ;print to terminal 
                            mov ebx, 1
                            mov dword [msg+1], 0xa      ;helps prints something out! 

                    ;Encountered problem here= prints out 'Answe' instead of integer '5'

                            push dword 2                ; store size of msg
                            push dword [msg]            ; push to stack contents of msg 
                            int 80h                    

                            add esp, 3                  ;clean stack (2 push calls *4)
                            int 80h

; I like labels :)

sys_exit:                            mov eax,1                   ;exit status
                                    mov ebx,0                        
                                    int 80h  
                                        nop

PS- If my line indenting sucks I'd like to know how I can improve it; IMHO learning assembly gets more appealing once you get over the initial learning "hump" :)

回答1:

First, thanks for the question-- it motivated me to study up in int 80h, something I wasn't familiar with before.

What does your program do in its current form? Does it print anything? If I'm executing it correctly in my head, I expect it to print the first message and then crash. Due to buffering, it might not even show the first message before the suspected crash.

int 80h/eax=4 maps to the write() function. Run 'man 2 write' to get the full documentation. The function prototype is:

ssize_t write(int fd, const void *buf, size_t count);

So, eax = 4, ebx = fd, ecx = buf, edx = count. If you want to be more pedantic with your code comments, "mov eax,4" means "write a string to a file" while "mov ebx,1" means "specify file #1, which maps to STDOUT (the terminal)".

I expect the first int 80h call to print something. The second int 80h call is suspect. At this point eax and ebx are unchanged. However, edx is also unchanged and that holds the string length of the first string. More problematic is that you are putting the value 5 into ecx. ecx holds a pointer to the string to be written, not a value to be written. So when you put 5 in there and call the interrupt, the int will try to print a string starting at address 0x00000005. This is almost certain to cause a segfault (crash).

Also, if I'm reading the int 80h docs correctly, the push/pop stack ops in your example don't have any relevance.

Printing a number as a string is a bit of a chore in ASM. You need to convert the number into a string and then you can print it using int 80h. Here's a way you can cheat if you just want to print a single digit:

  • in the .data section, declare a new byte called int2char: 'int2char: db 0'
  • after subtracting 5 from ecx, convert the digit to ASCII by adding 48 (this will give you '5' instead of 5)
  • store ecx into int2char
  • move the address of int2char into ecx
  • set edx to 1 since you only want to print 1 char

Converting numbers with more than one digit will be left as an exercise for you once you get this working.

Good luck!



回答2:

I think the push messages are relevant. Here is another example of a "hello world" program.

section     .text
    global _start                       ;must be declared for linker (ld)

_syscall:           
    int     0x80            ;system call
    ret

_start:                         ;tell linker entry point

    push    dword len       ;message length
    push    dword msg       ;message to write
    push    dword 1         ;file descriptor (stdout)
    mov     eax,0x4         ;system call number (sys_write)
    call    _syscall        ;call kernel

                         ;the alternate way to call kernel:
                         ;push   eax
                         ;call   7:0

    add     esp,12          ;clean stack (3 arguments * 4)

    push    dword 0         ;exit code
    mov     eax,0x1         ;system call number (sys_exit)
    call    _syscall        ;call kernel

                         ;we do not return from sys_exit,
                         ;there's no need to clean stack
section .data

    msg     db      "Hello, world!",0xa     ;our dear string
    len     equ     $ - msg                 ;length of our dear string

Now, i didn't write the code above, it came from here:

http://www.cin.ufpe.br/~if817/arquivos/asmtut/index.html#helloworld

So you can see that this author is able to move values onto the stack and call the kernel routine which will take params off the stack. This actually makes more sense to me, as I thought that's how params were passed to c functions. Passing params through specific registers makes no sense. What if a method has 30 parameters, how would you pass that through registers?

In any case, I have compiled the above code, linked it and ran it on my macbook pro and it worked fine.

Anyhow, as nice as the example above is, it doesn't teach you much. Here, I think, is a better example of how you would print out an arbitrary value from a register.

So what I want is not a pre-defined string, but a real programming construct; a variable.

Easy enough, you can define a 32bit variable by doing the following:

section .bss
    msg:     resd 2

This gives me a variable (or memory location) called "msg" that I can store stuff into. By declaring it with resd, I am defining the number of double-words that I want reserved. A double-word is 32 bits, so I declare that I want 2 double-word reserved. Why 2? I will tell you in a moment.

So far so good.

Now all I should need to do is move a value into that memory location and follow the example from hello, world, right? So I coded up this:

section     .text
    global _start                       ;must be declared for linker (ld)

_syscall:           
    int     0x80            ;system call
    ret

_start:                     ;tell linker entry point
    mov dword [msg], 'h'    ;move the letter h into my memory location
    mov dword [msg+1], 0xa  ;this is so important, but other author's gloss over it
                            ; the line terminator is essential in order to
                            ; print something out
    push    dword 2         ;message length
    push    dword msg       ;message to write
    push    dword 1         ;file descriptor (stdout)
    mov     eax,0x4         ;system call number (sys_write)
    call    _syscall        ;call kernel

                         ;the alternate way to call kernel:
                         ;push   eax
                         ;call   7:0

    add     esp,12          ;clean stack (3 arguments * 4)

    push    dword 0         ;exit code
    mov     eax,0x1         ;system call number (sys_exit)
    call    _syscall        ;call kernel

                         ;we do not return from sys_exit,
                         ;there's no need to clean stack
section .bss
    msg:     resd 1

So not too much has changed. I added the .bss section, which is for uninitialized data. Much more useful than static strings. We're moving a static value (the letter h) into my msg memory location, but you could just as easily move a register like this

mov [msg], eax

The next line is so important, but every "hello world" author just glosses over it. That 0xa is a line terminator that std out needs or it WILL_NOT display your values. This drove me crazy for a long time, trying to figure out why nothing of mine would print out. You NEED that line terminator. That is also why we need to define our variable to hold two values instead of just one. We need to store that line terminator.

The rest is simple, throw the params on the stack and make the call to the kernel. Now you have a true example of how to print out some arbitrary data from an actual variable data location. Enjoy!