The basic syntax structure of inline assembly in C is as follows:
The general format for basic inline assembly is:
asm [volatile] ("assembly_code" : [output_operands] : [input_operands] : [clobbered_registers]);
volatile: Optional keyword instructing the compiler not to optimize this assembly block (to avoid deletion or reordering).assembly_code: A string containing specific assembly instructions (e.g.,"movl $5, %eax").- Output Operands: C variables written to by the assembly instructions (format:
"constraint"(variable_name)). - Input Operands: C variables read by the assembly instructions (same format as above).
- Clobbered Registers: Registers modified by the assembly code (e.g.,
"eax","memory"), informing the compiler to avoid storing critical data in these registers.
GCC-Style Inline Assembly
Syntax:
asm [volatile] (
"instruction1\t"
"instruction2\t"
...
: output_operand_list
: input_operand_list
: clobber_list
);
MSVC-Style Inline Assembly
Syntax:
__asm {
instruction1
instruction2
; comment
}
Complete Example: Swapping Two Variables
GCC Implementation
void swap(int *a, int *b) {
asm volatile (
"movl %0, %%eax\t " // eax = *a
"movl %1, %%ebx\t " // ebx = *b
"movl %%ebx, %0\t " // *a = ebx
"movl %%eax, %1 " // *b = eax
: "+r" (*a), "+r" (*b) // Output operands (read-write)
: // No input operands
: "%eax", "%ebx" // Clobbered registers
);
}
MSVC Implementation
void swap(int *a, int *b) {
__asm {
mov eax, [a]
mov ebx, [b]
mov ecx, [eax]
mov edx, [ebx]
mov [eax], edx
mov [ebx], ecx
}
}
Modern GCC Extended Syntax: Constraint Details
| Constraint | Meaning | Example |
|---|---|---|
r | Any general-purpose register | asm("movl %0, %%eax" : : "r"(value)) |
m | Memory operand | asm("movl %0, %%eax" : : "m"(var)) |
+ | Read-write operand | asm("addl %1, %0" : "+r"(sum) : "r"(val)) |
& | Early clobber | asm("..." : "=&r"(result) : "r"(input)) |
Extended inline assembly supports more complex operations (e.g., multi-instruction blocks, conditional execution). Its syntax is:
asm [volatile] (
"label: instruction1\n\t"
"instruction2\n\t"
...
: [outputs]
: [inputs]
: [clobbers]
);
It supports labels, conditional jumps, loops, and other advanced control flow constructs, making it suitable for implementing complex assembly logic.
Complex Example: Computing Array Sum
int array_sum(int *arr, int len) {
int sum = 0;
asm volatile (
"xorl %%eax, %%eax\t " // Clear eax (used as sum)
"movl %1, %%ecx\t " // ecx = len
"movl %0, %%edi\t " // edi = arr
"rep stosl\t " // Placeholder—should be actual loop
"movl %%eax, %0 "
: "+r" (sum)
: "r" (len), "r" (arr)
: "%eax", "%ecx", "%edi"
);
return sum;
}
Example: Loop Accumulation Using Extended Inline Assembly
#include <stdio.h>
int sum(int n) {
int result = 0;
asm volatile (
"movl $0, %%eax\n\t" // eax = 0 (initialize result)
"movl %1, %%ecx\n\t" // ecx = n (loop counter)
"L1:\n\t" // Loop label
"addl $1, %%eax\n\t" // eax += 1 (accumulate)
"decl %%ecx\n\t" // ecx -= 1 (decrement counter)
"jnz L1\n\t" // Jump to L1 if ecx ≠ 0
: "=a" (result) // Output: eax → result
: "r" (n) // Input: n into any register (here ecx)
: "%eax", "%ecx", "%cc" // Clobbered: eax, ecx, condition codes
);
return result;
}
int main() {
printf("sum(5)=%d\n", sum(5)); // Output: sum(5)=5
return 0;
}
Calling Conventions Explained
A calling convention defines rules for function calls regarding parameter passing, register preservation, and stack cleanup. It is the core specification governing interaction between assembly and high-level languages. Different platforms and compilers support different conventions; common ones include cdecl, stdcall, and fastcall.
Comparison of Major Calling Conventions
| Convention | Parameter Passing | Stack Cleanup Responsibility | Typical Use Case | Register Preservation Rules |
|---|---|---|---|---|
cdecl | Right-to-left push onto stack (caller pushes args) | Caller (ret n) | C standard library (e.g., printf) | Caller saves eax, ecx, edx |
stdcall | Right-to-left push onto stack (caller pushes args) | Callee (ret n) | Win32 API (e.g., MessageBox) | Callee saves eax, ecx, edx |
fastcall | First two args in ECX/EDX; rest on stack | Callee | High-frequency functions (e.g., math libraries) | Callee saves ebx, esi, edi, ebp |
thiscall | this pointer in ECX; rest on stack | Callee | C++ member functions | Same as fastcall |
System V AMD64 ABI (x86-64)
- Integer arguments:
RDI,RSI,RDX,RCX,R8,R9 - Floating-point arguments:
XMM0–XMM7 - Return value:
RAX(integer),XMM0(floating-point) - Stack alignment: 16-byte boundary
Calling Convention Implementation Examples
cdecl Function Call
C function declaration: int __cdecl add(int a, int b);
Assembly implementation (GCC compiled):
section .text
global add
add:
push ebp
mov ebp, esp
mov eax, [ebp+8] ; First parameter a
add eax, [ebp+12] ; Add second parameter b
pop ebp
ret ; Caller cleans up stack
stdcall Function Call
C function declaration: int __stdcall sub(int a, int b);
Assembly implementation:
section .text
global sub
sub:
push ebp
mov ebp, esp
mov eax, [ebp+8] ; Parameter a
sub eax, [ebp+12] ; Subtract parameter b
pop ebp
ret 8 ; Clean up 8 bytes (2 × 4)
Parameter Passing Mechanisms
Parameter Passing Comparison
| Method | Applicable Scenario | Performance | Flexibility |
|---|---|---|---|
| Registers | Few parameters | Best | Limited |
| Stack | Many parameters / complex types | Slower | Flexible |
Example (x86-64, System V ABI)
For function int add(int a, int b, int c, int d, int e, int f):
a→rdib→rsic→rdxd→rcxe→r8f→r9
Parameters beyond the first six are pushed onto the stack.
x86-64 System V Parameter Passing Example
// C function: long func(int a, double b, int c);
asm volatile (
"movl %1, %%edi\t" // First arg a → EDI
"movsd %2, %%xmm1\t" // Second arg b → XMM1
"movl %3, %%esi\t" // Third arg c → ESI
"call func"
:
: "r"(a), "m"(b), "r"(c)
: "%edi", "%xmm1", "%esi"
);
Floating-Point Parameter Handling
Floating-point parameters are typically passed via SSE registers (x86-64) or ST registers (x86):
- x86-64 (System V ABI): First 8 floating-point parameters use
xmm0–xmm7. - x86 (cdecl): Floating-point parameters are pushed onto the stack (8-byte aligned).
Floating-Point Parameter Rules
- First 8 FP args passed via
XMM0–XMM7 - Integer and floating-point registers are counted independently when mixed
- Return values:
- Single/double precision:
XMM0 - Quad precision:
XMM0:XMM1
- Single/double precision:
Example
// C function: double calc(double a, int b, float c);
asm volatile (
"movsd %1, %%xmm0\t" // a → XMM0
"movl %2, %%edi\t" // b → EDI
"movss %3, %%xmm1\t" // c → XMM1
"call calc"
:
: "m"(a), "r"(b), "m"(c)
: "%xmm0", "%edi", "%xmm1"
);
Return Value Handling
Return values are typically passed as follows:
- Integer/Pointer:
eax(x86) orrax(x86-64) - Floating-point:
xmm0(x86-64) orst(0)(x86) - Large structs: Passed via hidden pointer (caller allocates memory; callee fills it)
Return Value Passing Rules
| Data Type | x86 (EAX) | x86-64 (RAX) | Float (XMM0) |
|---|---|---|---|
char/short | AL | AL | — |
int/pointer | EAX | RAX | — |
long long | EDX:EAX | RDX:RAX | — |
float | — | — | XMM0 |
double | — | — | XMM0 |
Struct Return Example
// C function: struct Point {int x,y;} get_point();
struct Point p;
asm volatile (
"call get_point\t"
"movdqu %%xmm0, %0" // Assume return via XMM0
: "=m"(p)
:
: "%xmm0"
);
Complex Return Value Handling
Large Struct Return (via Hidden Pointer)
// C function: struct BigStruct get_big_struct();
struct BigStruct { int data[100]; };
void wrapper(struct BigStruct *result) {
asm volatile (
"movq %1, %%rdi\t" // Hidden pointer → RDI
"call get_big_struct"
:
: "r"(result)
: "%rdi"
);
}
Assembly Implementation (x86-64)
; Function prototype: Pair make_pair(int a, int b)
_make_pair:
push rbp
mov rbp, rsp
sub rsp, 16 ; Allocate stack space (16-byte aligned)
mov DWORD PTR [rsp], edi ; a → edi (first parameter)
mov DWORD PTR [rsp+4], esi ; b → esi (second parameter)
lea rax, [rsp] ; Load address of struct into rax (return value)
mov rsp, rbp
pop rbp
ret
Mixed-Language Debugging Techniques
GDB Mixed Debugging Setup
Compilation Command:
gcc -g -o mixed_program main.c asm_code.s
GDB Debugging Commands:
# Set mixed breakpoints
break main.c:15 # C source breakpoint
break *0x4005a0 # Assembly address breakpoint
# View mixed call stack
bt full
# Monitor both C variables and assembly registers
info locals # C local variables
info registers # Assembly registers
x/10i $pc # View current assembly instructions
Key Commands
layout asm: Display assembly code window.break *0xaddress: Set breakpoint at assembly instruction address.info registers: View all register values.x/10x $esp: Inspect stack memory (10 double words).ni/si: Step next/step into instruction.
Debugging Tips
Breakpoint Strategy
- Set breakpoints at C function entries
- Set address breakpoints at assembly function entries
- Set watchpoints at key parameter-passing locations
Typical Debugging Workflow
(gdb) break main # C program entry
(gdb) run
(gdb) stepi # Enter assembly code
(gdb) info registers # Check register state
(gdb) x/5i $pc # View next 5 instructions
(gdb) finish # Finish current assembly function
(gdb) next # Continue with C code
Common Issue Diagnosis
Parameter Passing Errors
# Verify parameter passing
(gdb) x/4wx $esp # Inspect stack arguments
(gdb) info registers # Check register arguments
Return Value Anomalies
# Validate return value handling
(gdb) p $eax # Check integer return value
(gdb) p/x $xmm0 # Check floating-point return value
Stack Imbalance Issues
# Monitor stack pointer changes
(gdb) info registers esp
(gdb) x/10wx $esp
Comprehensive Practical Example: Optimized Matrix Multiplication
C Main Program
#include <stdio.h>
void matmul_asm(float *A, float *B, float *C, int n);
int main() {
float A[4][4] = {...};
float B[4][4] = {...};
float C[4][4] = {0};
matmul_asm(&A[0][0], &B[0][0], &C[0][0], 4);
// Print results...
return 0;
}
Assembly Implementation (matmul.asm)
section .text
global matmul_asm
matmul_asm:
; Parameters: rdi=A, rsi=B, rdx=C, rcx=n
push rbp
mov rbp, rsp
; ... matrix multiplication implementation ...
pop rbp
ret
Compilation and Linking
nasm -f elf64 matmul.asm -o matmul.o
gcc -O3 -march=native -o program main.c matmul.o
System Call Wrapping
System calls are interfaces for user-space programs to interact with the kernel, triggered via software interrupts (e.g., x86 int 0x80) or dedicated instructions (e.g., x86-64 syscall). High-level languages (like C) wrap system calls via standard libraries (e.g., glibc), while assembly can invoke them directly.
C Interface Example
int my_write(int fd, const void *buf, size_t count);
Implementation (asm_write.s):
section .text
global my_write
my_write:
mov rax, 1 ; sys_write syscall number
ret
Debugging Tips
# Trace system calls
(gdb) catch syscall write
(gdb) break my_write
(gdb) commands
>silent
>printf "fd=%d, buf=%p, count=%zu", $rdi, $rsi, $rdx
>continue
>end
System Call Implementation Principles
- x86 (32-bit): Uses
int 0x80;eaxholds syscall number;ebx,ecx, etc., hold parameters. - x86-64 (64-bit): Uses
syscall;raxholds syscall number;rdi,rsi, etc., hold parameters.
Example: Direct write System Call (x86-64)
The write syscall number on x86-64 is 1, with parameters:
rdi: File descriptor (STDOUT_FILENO = 1)rsi: Buffer addressrdx: Write length
section .data
msg db 'Hello, System Call!', 0xA
len equ $ - msg
section .text
global _start
_start:
; System call: write(1, msg, len)
mov rax, 1 ; syscall #1 (write)
mov rdi, 1 ; stdout
mov rsi, msg ; buffer address
mov rdx, len ; length
syscall ; trigger syscall
; Exit program (syscall #60)
mov rax, 60 ; exit
xor rdi, rdi ; status 0
syscall
Compile and Run:
nasm -f elf64 sys_write.asm -o sys_write.o
ld sys_write.o -o sys_write
./sys_write # Output: Hello, System Call!
High-Level Language System Call Wrapping (C)
C standard libraries (e.g., glibc) provide user-friendly wrappers:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count) {
return syscall(SYS_write, fd, buf, count); // Calls glibc's syscall wrapper
}
Comprehensive Practical Example: Optimizing Matrix Multiplication
Inline assembly can optimize the inner loop of matrix multiplication to improve performance. Assuming N×N matrices, the goal is to reduce memory accesses and utilize registers for intermediate results.
Original C Implementation
#define N 1024
int A[N][N], B[N][N], C[N][N];
void matrix_multiply() {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
int sum = 0;
for (int k = 0; k < N; k++) {
sum += A[i][k] * B[k][j];
}
C[i][j] = sum;
}
}
}
Optimized Version (Loop Unrolling + Register Caching)
Unroll the inner loop to reduce branch misprediction and cache A[i][k] and B[k][j] in registers:
void matrix_multiply_optimized() {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
int sum = 0;
// Unroll inner loop (k=0,1,2,3)
for (int k = 0; k < N; k += 4) {
int a0 = A[i][k];
int a1 = A[i][k+1];
int a2 = A[i][k+2];
int a3 = A[i][k+3];
int b0 = B[k][j];
int b1 = B[k+1][j];
int b2 = B[k+2][j];
int b3 = B[k+3][j];
sum += a0*b0 + a1*b1 + a2*b2 + a3*b3;
}
C[i][j] = sum;
}
}
}
Inline Assembly Implementation (x86-64)
Use GCC extended inline assembly to optimize the inner loop directly:
void matrix_multiply_asm() {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
int sum = 0;
asm volatile (
"xor %%rcx, %%rcx\n\t" // k = 0 (loop counter)
"L_loop:\n\t"
"cmp %[N], %%rcx\n\t" // Compare k with N
"jge L_end\n\t" // Exit if k >= N
"mov %[A_i_k], %%eax\n\t" // Load A[i][k] into eax
"mov %[B_k_j], %%ebx\n\t" // Load B[k][j] into ebx
"imul %%ebx, %%eax\n\t" // eax = A[i][k] * B[k][j]
"add %%eax, %[sum]\n\t" // sum += product
"inc %%rcx\n\t" // k++
// Compute next addresses (row-major layout, int = 4 bytes)
"lea (%[A],%%rcx,4), %[A_i_k]\n\t" // A[i][k] = A + i*N + k
"lea (%[B],%%rcx,4), %[B_k_j]\n\t" // B[k][j] = B + k*N + j
"jmp L_loop\n\t"
"L_end:\n\t"
: [sum] "+r" (sum)
: [A] "r" (A), [B] "r" (B), [i] "r" (i), [j] "r" (j), [N] "r" (N)
: "%rax", "%rbx", "%rcx"
);
C[i][j] = sum;
}
}
}
Note: The above example assumes simplified address calculation; real implementations must account for full 2D indexing (
A[i*N + k],B[k*N + j]).
Summary and Outlook
The integration of assembly and high-level languages is a core skill in low-level development. Inline assembly enables optimization of critical paths, understanding calling conventions clarifies function interactions, and mixed debugging resolves complex issues.
In the future, as compiler optimization advances (e.g., auto-vectorization, loop unrolling), the need for hand-written assembly may diminish. However, it will remain indispensable in domains such as:
- High-performance computing
- Embedded systems development
- Reverse engineering
Developers should master:
- Inline assembly syntax and register management
- Calling convention rules and parameter passing mechanisms
- Mixed-language debugging tools and techniques
- Low-level implementation and wrapping of system calls
By combining theory with practice, developers can effectively leverage the strengths of both assembly and high-level languages to build high-performance, highly reliable software systems.



