You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

被调用者如何识别参数通过寄存器而非栈传递?附汇编案例疑问

Great questions! Let's break this down in a way that makes sense for your Ubuntu x64 (WSL) setup.

1. How does the callee know parameters are passed via registers instead of the stack?

This all comes down to calling conventions—a shared rulebook that compilers, assemblers, and CPUs strictly follow. Think of it like a pre-agreed protocol:

  • Every platform has its own convention. For x86-64 Linux/Unix systems (like your WSL Ubuntu), we use the System V AMD64 Calling Convention. Windows uses a different one, but that’s not relevant here.
  • When a function is compiled, it’s built to expect parameters exactly where the convention specifies. For System V, that means the first 6 integer/pointer parameters live in registers %rdi, %rsi, %rdx, %rcx, %r8, %r9—not the stack.
  • If the caller breaks this rule (say, pushing a parameter to the stack when it should go in a register), the callee will look in the wrong place, and your program will crash immediately. This is why hand-written assembly or cross-language calls must stick strictly to the convention.
2. Explaining the assembly code: No push instructions, and how the callee finds parameters

First, let’s recap your code and the generated assembly for context:

Original C Program

#include <cstdio>
int main(int argc, char* argv[]) {
    int n = argc;
    if (n > 1) {
        n = 1;
    }else {
        n = -1;
    }
    printf("%d\n", n);
    return 0;
}

Compiled Assembly (with g++ main.cpp -S -O1)

subq $8, %rsp
cmpl $1, %edi
setg %dl
movzbl %dl, %edx
leal -1(%rdx,%rdx), %edx
movl $.LC0, %esi
movl $1, %edi
movl $0, %eax
call __printf_chk
movl $0, %eax
addq $8, %rsp
ret

Core Context: System V AMD64 Calling Convention Rules

For your Ubuntu x64 setup, the key rules here are:

  • The first 6 integer/pointer parameters go into registers (in order: %rdi, %rsi, %rdx, %rcx, %r8, %r9). Only parameters beyond the 6th get pushed to the stack.
  • Before calling a function, the stack pointer %rsp must be 16-byte aligned. This is a hard requirement for x86-64.

Line-by-Line Breakdown

  1. subq $8, %rsp
    When main is called, the call instruction pushes an 8-byte return address to the stack, making %rsp misaligned by 8 bytes. Subtracting 8 bytes brings it back to 16-byte alignment—this is just setup for the upcoming call __printf_chk, not parameter passing.

  2. Calculating the value of n

    • cmpl $1, %edi: Compare argc (which lives in %rdi, per the calling convention for main's first parameter) to 1.
    • setg %dl: If argc > 1, set the 8-bit register %dl to 1; otherwise set it to 0.
    • movzbl %dl, %edx: Zero-extend %dl to the 32-bit %edx (so we have a proper integer value: 1 or 0).
    • leal -1(%rdx,%rdx), %edx: A clever compiler optimization! This calculates rdx*2 -1. If rdx is 1, we get 1; if it's 0, we get -1. This replaces the original if-else with a single arithmetic operation—faster and cleaner.
  3. Setting up parameters for __printf_chk
    You’ll notice this isn’t the standard printf—it’s __printf_chk, a security-hardened version used by GCC. Its signature is roughly int __printf_chk(int flag, const char *format, ...). Following the calling convention:

    • movl $1, %edi: Put the flag parameter (set to 1 for standard usage) into %rdi (first parameter register).
    • movl $.LC0, %esi: Put the address of the format string "%d\n" (stored at .LC0) into %rsi (second parameter register).
    • %edx already holds our calculated n value—this is the third parameter, which goes into %rdx per the convention.
    • movl $0, %eax: For variadic functions (like printf and its variants), we set %eax to 0 to tell the callee we aren’t passing any floating-point parameters (those use XMM registers).
  4. Why no push instructions?
    All parameters fit into the first 3 registers specified by the calling convention—there’s no need to push anything to the stack. The only stack operations are for maintaining alignment, not passing data.

How __printf_chk identifies parameters

Since __printf_chk was compiled to follow the System V convention, it knows exactly where to look:

  • It grabs the flag parameter from %rdi.
  • It grabs the format string from %rsi.
  • It grabs the integer to print from %rdx.
  • For any additional variadic parameters, it would follow the convention to pull them from the next registers or the stack (though there are none here).

内容的提问来源于stack exchange,提问作者kayochin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:22:28