Printing Variable to Command Line Using Assembly in Linux

Printing variable to command line using assembly in Linux

You're trying to print out the value of the pointer to the string instead of printing the string. You want to do this instead.

pop ebx     ;argument number
pop ebx ;program name
pop ebx ;pointer to the first argument

mov ecx,ebx ;load the pointer into ecx for the write system call

mov eax,4 ;load the other registers for the write system call
mov ebx,1
mov edx,3
int 80h

mov eax,1
mov ebx,0
int 80h

Printing Commandline Arguments x86 Assembly

On POSIX systems if you start a program from a shell the shell will perform so-called globbing, expanding the * and replacing it with the matching files; the result will then be used as the arguments for the invocation of your program.

You cannot do anything to prevent it from your program, the arguments you see are indeed those it got from the shell (IOW, they are the arguments that have been passed to exec).

If you want to pass a literal * to your program when starting it from a shell, you'll have to quote it.

./asm5 me you them us "*"

Of course none of this will happen if some other program launches your program passing the arguments straight to exec, without the shell getting in the way.


Replaced:

; the loop
cmp ecx, eax ; if ecx not equal to eax
jne begin_for_loop ; jmp to loop, else exit

With:

; the loop
cmp ecx, eax ; if ecx not equal to eax
jmp begin_for_loop ; jmp to loop, else exit

And I found a bigger issue where the program prints ENV variables to the screen.

This comes from the fact that you are ignoring the NULL that terminates the argv arguments list, and you are going on reading what's next; this in your case happens to be the environment memory block.

How to print a string to the terminal in x86-64 assembly (NASM) without syscall?

This is a good exercise. You will use syscall (you cannot access stdout otherwise), but you can do a "bare-metal" write without any external library providing the output routine (like calling printf). As an example of the basic "bare-metal" write to stdout in x86_64, I put together a example without any internal or system function calls:

section .data
string1 db 0xa, " Hello StackOverflow!!!", 0xa, 0xa, 0

section .text
global _start

_start:
; calculate the length of string
mov rdi, string1 ; string1 to destination index
xor rcx, rcx ; zero rcx
not rcx ; set rcx = -1
xor al,al ; zero the al register (initialize to NUL)
cld ; clear the direction flag
repnz scasb ; get the string length (dec rcx through NUL)
not rcx ; rev all bits of negative results in absolute value
dec rcx ; -1 to skip the null-terminator, rcx contains length
mov rdx, rcx ; put length in rdx
; write string to stdout
mov rsi, string1 ; string1 to source index
mov rax, 1 ; set write to command
mov rdi,rax ; set destination index to rax (stdout)
syscall ; call kernel

; exit
xor rdi,rdi ; zero rdi (rdi hold return value)
mov rax, 0x3c ; set syscall number to 60 (0x3c hex)
syscall ; call kernel

; Compile/Link
;
; nasm -f elf64 -o hello-stack_64.o hello-stack_64.asm
; ld -o hello-stack_64 hello-stack_64.o

output:

$ ./hello-stack_64

Hello StackOverflow!!!

For general use, I split the process into two parts (1) getting the length and (2) writing to stdout. Below the strprn function will write any string to stdout. It calls strsz to get the length while preserving the destination index on the stack. This reduces the task of writing a string to stdout and prevents a lot of repitition in your code.

; szstr computes the lenght of a string.
; rdi - string address
; rdx - contains string length (returned)
section .text
strsz:
xor rcx, rcx ; zero rcx
not rcx ; set rcx = -1 (uses bitwise id: ~x = -x-1)
xor al,al ; zero the al register (initialize to NUL)
cld ; clear the direction flag
repnz scasb ; get the string length (dec rcx through NUL)
not rcx ; rev all bits of negative -> absolute value
dec rcx ; -1 to skip the null-term, rcx contains length
mov rdx, rcx ; size returned in rdx, ready to call write
ret

; strprn writes a string to the file descriptor.
; rdi - string address
; rdx - contains string length
section .text
strprn:
push rdi ; push string address onto stack
call strsz ; call strsz to get length
pop rsi ; pop string to rsi (source index)
mov rax, 0x1 ; put write/stdout number in rax (both 1)
mov rdi, rax ; set destination index to rax (stdout)
syscall ; call kernel
ret

To further automate general output to stdout NASM macros provide a convenient solution. Example strn (short for string_n). It takes two arguments, the addresses of the string, and the number of characters to write:

%macro  strn    2
mov rax, 1
mov rdi, 1
mov rsi, %1
mov rdx, %2
syscall
%endmacro

Useful for indents, newlines or writing complete strings. You could generalize further by passing 3 arguments including the destination for rdi.

Print 2nd command line argument

dword [ebp+12] is a pointer to an array of string pointers. The first element of that array is a pointer to the first string, the second element is a pointer to the second string etc. Each pointer is 32-bits (4 bytes) wide.

To get a pointer to the second string would require getting the pointer at dword [ebp+12] + 4. You can't do that directly in x86 addressing. You can do it by moving dword [ebp+12] into a register like EAX, add 4 to it (since a pointer is 4 bytes wide) and then dereference that to get a pointer of the second string.

Replace:

mov eax, dword [ebp+16]  ; prints 1st letter of 2nd argument
mov al, byte[eax]
call print_string

With:

mov eax, dword [ebp+12]  
mov eax, [eax+4] ; EAX = pointer to 2nd argument
call print_string

This would print out the second argument. The first argument can be printed out with:

mov eax, dword [ebp+12]  
mov eax, [eax] ; EAX = pointer to 1st argument
call print_string

Of course mov eax, [eax+8] would get the 3rd argument and so on.

You can't use print_string to print a single character in a register (like AL). EAX must be a pointer to a NUL(\0) terminated string.


Something else you can do is use scaled index addressing to step through an array (like your arguments):

mov ebx, dword [ebp+12]  
xor esi, esi ; Index of first argument (index=0)
mov eax, [ebx+esi*4] ; EAX = pointer to 1st argument
call print_string
inc esi ; Next argument (index=1)
mov eax, [ebx+esi*4] ; EAX = pointer to 2nd argument
call print_string
inc esi ; Next argument (index=2)
mov eax, [ebx+esi*4] ; EAX = pointer to 3rd argument
call print_string

With this idea you can probably see how you can create a loop that goes through the arguments. I leave that as an exercise for the reader. This is another handy quick reference for addressing modes.

Linux 64 command line parameters in Assembly

It looks like section 3.4 Process Initialization, and specifically figure 3.9, in the already mentioned System V AMD64 ABI describes precisely what you want to know.

Print environment variables in NASM

Take a look at the following related post: Why does this NASM code print my environment variables?.

You can also take a look at the startup state of a Linux binary.

In short, environment variables are listed after a NULL value which follows command line arguments.

Getting command line parameters from an assembly program

With the help from @Michael, I was able to track down the problem.

Using %ebp as argv as @Michael suggested (he used %eax though). Another problem was that I needed to compare the value of (%ebp) with 0 (the null terminator) and end the program at that point.

Code:

    movl 8(%esp), %ebp  /* Get argv.  */

pr_arg:
cmpl $0, (%ebp)
je endit

pushl %ecx
pushl (%ebp)
pushl $output2
call printf
addl $8, %esp /* remove output2 and current argument. */
addl $4, %ebp

popl %ecx
loop pr_arg

ret

Passing parameter in command line, Assembly Programming

Your while loop doesn't read any characters! You retrieve these using mov dl,[eax].

As you can see from the code below, there's no need to use a temporary input variable.

  xor   ebx, ebx            ;Result
while:
movzx edx, byte ptr [eax] ;Read 1 character
test dl, dl ;Test for end of string
jz print ;End found
sub dl, 0x30 ;Go from character to value [0,9]
imul ebx, 10 ;Result x10
add ebx, edx ;Add new digit
inc eax ;To next character
jmp while


Related Topics



Leave a reply



Submit