为什么内联汇编不需要像 .data 或 .text 这样的节指令

mor*_*und 0 x86 assembly inline-assembly

作为一个新手,我正在遵循教程。一种是在 VS 2022 的内联汇编中将字符串中的所有字符大写:

int main()
{
    char mystr[] = "Hello World:";

    _asm
    {
        mov ecx, length mystr
    my: cmp [mystr + ecx], 'a';
        jl nocap;
        cmp [mystr + ecx], 'z';
        ja nocap;
        sub [mystr + ecx], 32;

    nocap:
    loop my
    }

    std::cout << mystr;
Run Code Online (Sandbox Code Playgroud)

我的问题是:为什么这个程序集不需要节,例如 .data、.text 或 _start:示例中可能混合了 x86 asm 和 Linux asm。

Pet*_*des 6

因为它是内联汇编!它就在 C++ 函数体内,编译器选择将函数的机器代码放入哪个部分。( .text)。

出于同样的原因,您不需要节指令,C++ 程序不需要手动节指令,或者 GNU C __attribute__((section(".text")))- C++ 编译器对于放置内容的位置有工作默认值。


事实上,MSVC 甚至不允许您切换部分或用于在块db内发出任意字节asm{};它不是一个完整的汇编程序,因为它必须解析并理解您的汇编程序才能知道它可能修改哪些寄存器,因此它知道要保存什么。

GNU C 内联汇编由您决定告诉编译器内联汇编的输出/输入/破坏是什么,并允许您发出汇编器将汇编的任意文本。

GCC 实际上是通过生成一个.s文本文件并运行as来汇编它来工作的。GNU C 内联asm ("add %1, %0" : "=r"(dst) : "r"(src))工作方式有点像编译时 printf 将自定义文本格式化为该汇编文件。您可以做一些会破坏以下编译器生成的代码的事情,例如在不使用.pushsection/的情况下切换部分.popsection以返回到编译器所在的部分。或者将 asm 语法切换为编译器使用的其他语法。

或者做一些有用的事情,例如使用.pushsection .data和发出一些字节.data,然后返回。Linux 内核对此进行了一些利用。例如,在arch/x86/asm/alternative.h中,它们.pushsection .smp_locks记录lock原子 RMW 指令的前缀地址(用于.long发出 4 个字节),因此如果内核在只有一个 CPU 的机器上启动,它可以将这些lock前缀修补为nop或虚拟前缀,因为相关指令都是原子的。中断,只是不能同时运行其他 CPU。


您也不需要编写自己的程序_start(Linux 中进程入口点的标准名称),因为 C++ 编译器将您的程序与 CRT 启动代码链接,该代码提供了一个调用main并退出的main代码(如果main返回)。如果您想编写自己的代码,您可以在 Linux 的 GNU C 内联汇编中使用gcc -nostdlib(这意味着-nostartfiles):How Get argument value using inline assembly in C without Glibc?

对于 MSVC,您需要使用单独的.asm文件;MSVC 不允许在全局范围内内联汇编。或者,也许您可​​以使用一个_declspec(naked)函数来定义 WinMain 或任何可执行文件的实际入口点,并使用手写的 asm 指令进行必要的设置,例如调用标准库 init 函数,然后call main/ push eax/ call ExitProcess(或者实际上call exit使确保进行清理,例如刷新 stdio 缓冲区)。