作为一个新手,我正在遵循教程。一个是大写字符串中的所有字符,在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;
我的问题是:为什么此程序集不需要.data、.text或_start等节:示例中可能混合使用了x86 asm和Linux asm。
1条答案
按热度按时间tvokkenx1#
因为它是内联的asm!它就在C函数体的内部,编译器会选择将函数的机器码放入哪一部分。(
.text
)。你不需要段指令,就像C程序不需要手动段指令或GNU C
__attribute__((section(".text")))
一样-- C++编译器有工作默认值来放置东西。事实上,MSVC甚至不允许切换节或使用
db
在asm{}
块中发出任意字节;它不是一个完整的汇编程序,因为它必须解析和 * 理解 * 您的asm,以了解它可能修改哪些寄存器,因此它知道要保存哪些内容。GNU C inline asm leave由你来告诉编译器你的inline asm的输出/输入/乱码是什么,并允许你发出汇编程序将汇编的任意文本。
GCC的工作原理是生成一个
.s
文本文件,然后运行as
对其进行汇编。GNUC内联asm ("add %1, %0" : "=r"(dst) : "r"(src))
的工作原理类似于编译时printf将自定义文本格式化到该汇编文件中。您 * 可以 * 做一些会破坏以下编译器生成的代码的事情,例如,在不使用.pushsection
/.popsection
的情况下切换节以返回到编译器所在的节。或者将asm语法切换为编译器正在使用的语法之外的语法。或者做一些有用的事情,比如使用
.pushsection .data
并在.data
中发出一些字节,然后返回。Linux内核在某种程度上利用了这一点。例如,在arch/x86/asm/alternative. h中,它们.pushsection .smp_locks
记录了原子RMW指令的lock
前缀的地址(使用.long
发出4个字节),因此如果内核在只有一个CPU的机器上引导,则它可以将那些lock
前缀修补为nop
或伪前缀,因为相关指令都是原子写中断,而不是同时运行的其他CPU。您也不需要编写自己的
_start
(Linux中进程入口点的标准名称),因为C++编译器将程序与CRT启动代码链接,CRT启动代码提供一个调用main
的启动代码,如果main
返回,则退出时返回main
的返回值。如果您想编写自己的程序,您可以在GNU C inline asm for Linux中使用gcc -nostdlib
(这意味着-nostartfiles
):How Get arguments value using inline assembly in C without Glibc?对于MSVC,您需要使用一个单独的
.asm
文件; MSVC不允许在全局范围内内联asm,或者您可以使用_declspec(naked)
函数来定义WinMain或任何可执行文件的实际入口点,并使用手写的asm指令进行必要的设置,如调用标准库init函数,然后是call main
/push eax
/call ExitProcess
(或者实际上是call exit
,以确保进行清除,如刷新stdio缓冲区)。