Mostly for my own amusement: how to invoke a system call on Linux to write "Hello world" to the screen, without going through C's puts(), C++'s iostreams, or even glibc's write().
int main() {
constexpr char data[] = "Hello world\n";
constexpr auto size = sizeof data;
asm volatile (
"movq $1, %%rax \n\t" // 1 is the constant for SYS_write
"movq $1, %%rdi \n\t" // 1 is the constant for stdout
"movq %0, %%rsi \n\t" // Our pointer
"movq %1, %%rdx \n\t" // Our size
"syscall" // Execute
: // No output variables
: "r"(data) // Inputs: pass data via a register
, "i"(size) // pass size as if it were a constant
: "%rax", "%rdi", "%rsi", "%rdx" // The registers we clobber
);
}
The general form of this funky block is:
asm volatile(
template,
: inputs
: outputs
: clobbers
);
The "r" in front of the first output means "pass this in a register". The "i" in front of the second output means "treat this as a constant". There are many other such labels. Inputs and outputs can be referenced via %0, %1, ... . Outputs are listed first. Here, since we do not declare outputs, %0 refers to our first input.
Compiling this with gcc on Debian 13 results in the following assembly output (you can see precisely where it spliced in our exact assembly code):
.file "test.cpp"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movabsq $8031924123371070792, %rax
movq %rax, -21(%rbp)
movabsq $2925166706259744, %rax
movq %rax, -16(%rbp)
movq $13, -8(%rbp)
leaq -21(%rbp), %rcx
movl $13, %r8d
#APP
# 5 "test.cpp" 1
movq $1, %rax
movq $1, %rdi
movq %rcx, %rsi
movq %r8, %rdx
syscall
# 0 "" 2
#NO_APP
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Debian 14.2.0-19) 14.2.0"
.section .note.GNU-stack,"",@progbits
Pretty neat, eh.