I'm on an Ubuntu 22.04 x86_64 system, kernel 6.5.0-15-generic.
I'm learning how to make simple programs that make system calls by using GAS AT&T assembly format.
This assembly program is in the file hello.s:
.section .rodata
msg: .ascii "Hello, World!\n"
.set msglen, (. - msg)
.section .text
.global main
main:
mov $1, %rax
mov $1, %rdi
lea msg(%rip), %rsi
mov $msglen, %rdx
syscall
mov $60, %rax
mov $0, %rdi
syscall
The way I obtain the executable is this:
I assemble to get the object file with
as hello.s -o hello.oI link to get the executable with
gcc hello.o -o hello
The program works fine. I just have some doubts about the following:
What if I had used a file named
hello.Sinstead ofhello.s? Would it have been different doing something like:as hello.S -o hello.o gcc hello.o -o helloAre both forms correct?
Why is it that when I do something like
gcc -S demo.iI getdemo.sand notdemo.S?My goal was to create a simple classic main program in assembly that makes the write system call to write "Hello World". Is my code 100% correct?
You can also assemble and link your file in one shot by just doing
gcc -o hello hello.s.And this is related to your question.
gcclooks at input file suffixes to decide how to handle them. A.sfile gets fed directly to the assembler. If it is named.Sinstead, then it gets filtered through the C preprocessor and then fed to the assembler.So, if you would like to use C preprocessor features in your assembly source code (e.g.
#define), then name your file with.Sand usegcc(orclang) to build it. If you don't want that feature, then keep using.s.The compiler generates assembly that can go straight to the assembler without needing further preprocessing, so its assembly output files are named
.srather than.S.Your code itself looks fine as far as correct operation. There are some things you could improve related to efficiency.
Most x86-64 instructions with 64-bit operands are one byte longer than their 32-bit counterparts, due to the need for a REX prefix byte. So often it is preferable to use the 32-bit form instead when it will work, and take advantage of the fact that an instruction with a 32-bit register destination operand will automatically zero-extend it to 64 bits. (Why do x86-64 instructions on 32-bit registers zero the upper part of the full 64-bit register?) Thus
mov $1, %eaxis exactly equivalent tomov $1, %rax, but the former is preferred because it saves one byte of code size.Zeroing a register is more efficient with
xor %edi, %edi(which, as noted above, actually zeros all of%rdi). What is the best way to set a register to zero in x86 assembly: xor, mov or and?