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
History Command Works in a Terminal, But Doesn't When Written as a Bash Script
Bash Echo with an $ Character Outside the String
(Master) at End of Terminal Prompt
Macros for Gcc/G++ to Differentiate Linux and MAC Osx
Writing to Serial Port from Linux Command Line
What Are Ld-Linux.So.2 and Linux-Gate.So.1
How to Run R Scripts on Servers Without X11
Why Does '/Proc/Meminfo' Show 32Gb When Aws Instance Has Only 16Gb
About the Memory Layout of Programs in Linux
Explicitly Invoke Sig_Dfl/Sig_Ign Handlers on Linux
-Bash: /Usr/Bin/Yum: /Usr/Bin/Python: Bad Interpreter: No Such File or Directory
How to Set Up Curl to Permanently Use a Proxy
PDF Compare on Linux Command Line
Automating Amazon Ebs Snapshots Anyone Have a Good Script or Solution for This on Linux