If you have ever written assembly for the GNU Assembler (GAS), you may have noticed that files sometimes have an
.S extension and sometimes
.s. This is not a meaningless distinction, and you could have a frustrating time if you accidentally use the wrong one.
.S indicates that the file contents should be run through the preprocessor, while the lowercase
.s indicates that the file contents should be assembled directly. Let’s take a look at an example in x86 assembly.
Note: GAS uses AT&T syntax by default for x86 assembly, which is what we use below. You can instruct the assembler to use Intel syntax with the
.global _start .text _start: mov $1, %rax mov $1, %rdi mov $message, %rsi mov $10, %rdx syscall mov $60, %rax xor %rdi, %rdi syscall message: .ascii "lowercase\n"
What does this program do? It is a basic “Hello World” x86 assembly program. The first section of
_startis calling the
writeLinux syscall by loading the syscall number (
$1) into the 64-bit general purpose accumulator register (
%rax), the first argument to the syscall (
$1) into the destination index register (
.asciitext into the source index register (
%rsi), and finally the length of our message into the data register (
%rdx). Then, we invoke the syscall. The subsequent section does a similar step, but this time with the
$60) and the xor of
%rdiwith itself (which will always be
0) to let the system know we exited successfully.
We can assemble, link, and run this program with the following commands:
gcc -c lowercase.s && ld lowercase.o && ./a.out
We didn’t use any C preprocessor directives, so this worked as expected. Let’s look at a variation of this program, this time with the uppercase
#define UPPER .global _start .text _start: mov $1, %rax mov $1, %rdi mov $message, %rsi mov $10, %rdx syscall mov $60, %rax xor %rdi, %rdi syscall message: #ifdef UPPER .ascii "uppercase\n" #else .ascii "lowercase\n" #endif
Now we have defined
UPPER and added conditional behavior based on it being defined. Let’s run this it and see what happens:
gcc -c uppercase.S && ld uppercase.o && ./a.out
Now remove the
#define UPPER directive and run again.
Lastly, let’s change the file name from
uppercase.s and try to execute it.
gcc -c uppercase.s && ld uppercase.o && ./a.out
Though we don’t have
#define UPPER in the program, we still get the
uppercase output because our program did not go through the preprocessor, so directives were not honored and the first
.message declaration was used.
Send me a message @hasheddan on Twitter for any questions or comments!