gpt4 book ai didi

在 X64 gcc 内联 asm 中调用 scanf

转载 作者:太空宇宙 更新时间:2023-11-04 10:09:57 24 4
gpt4 key购买 nike

我有一个动态分配的 2d int 数组,称为 image,还有一个格式字符串称为 format。然后我使用两个嵌套的 for 循环从标准输入中获取输入,并将它们存储在二维数组中。所以我可以动态地解析来自不同长度的输入的整数。例如,如果我有一个 3x3 二维数组,我将需要使用内联汇编将数组中的元素地址推送 9 次,然后推送到格式字符串。然后我调用 scanf,并在完成时平衡堆栈。

顺便说一句:假定数组的宽度和高度已知。

这里是我在Windows上的代码(X64系统,编译成x32代码)。它工作正常。

for (int i = 0; i < height; i++) {
for (int j = width-1; j >=0; j--) {
int tmp_addr = (int)&image[i][j];
__asm push tmp_addr;
}
int pop_size = (width+1) * 4;
__asm {
push format;
call func_scanf;
mov read_size, eax;
add esp, pop_size;
}
}

移植到Linux(X64系统,X64代码编译)时,代码没有运行。

for (int i = 0; i < height; i++) {
for (int j = width-1; j >=0; j--) {
long tmp_addr = (long)&image[i][j];
//__asm push tmp_addr;
__asm__ __volatile__(
"push %0\n\t"
::"g"(tmp_addr)
);
}

int pop_size = (width+1) * sizeof(long);
/*__asm {
push format;
call func_scanf;
mov read_size, eax;
add esp, pop_size;
}*/
__asm__ __volatile__(
"push %0\n\t"
"call *%1\n\t"
"mov %%rax,%2\n\t"
"add %3,%%rsp"
::"g"(format),"g"(func_scanf),"g"(read_size),"g"(pop_size)
:"%rax","%rsp"
);
}

执行此代码时出现段错误。会出什么问题?谢谢!

最佳答案

Linux 上的 x86_64 代码使用 a completely different calling convention比 Windows 上的 x86 代码。特别是,它会在开始使用堆栈之前尝试在寄存器中传递许多参数。此外,可变参数有一些微妙的额外规则(例如,您必须在 rax 中指定实际使用的 XMM 寄存器的数量,如果没有使用则为 0)。

scanf 期望在寄存器中找到前六个指针参数,但您将它们放在堆栈中,并且寄存器包含垃圾值(无论调用时碰巧在那里);当取消引用其中任何一个以写入读取值时,您会遇到段错误。

此外,现代编译器通常不使用rbp 作为帧指针来访问局部变量和参数,帧指针被省略,通过rsp 访问局部变量。随着你的推送,你在编译器不知道的情况下移动了堆栈指针,现在你的推送和函数调用返回之间的每个堆栈访问都将被破坏。你hand try to hand-hold the compiler around this ,但这是一件肮脏的事情,而且容易崩溃。

更糟糕的是:如果 gcc 认为您的函数是一个叶函数(如果唯一的函数调用在您的汇编代码中,它可能会这么认为,这对编译器是不透明的)它可能正在利用 red zone , 将内容置于 rsp 的当前值之下。您的推送和函数调用可能会覆盖此数据。你can try to fight even this ,但同样,这是丑陋的东西。

所以:很明显为什么你的代码不起作用,而且很明显要让它在 x86_64 调用约定上正确工作是相当复杂的 - 你必须将东西放在不同的寄存器或堆栈上,具体取决于迭代,并找到一种方法告诉 gcc 你正在弄乱堆栈指针并避免使用红色区域。

我不清楚的是:这件事有什么意义?如果您需要读取许多值,如果值的数量是固定的,您可以在纯 C 中执行 scanf 的“正常”调用。相反,如果要读取的值的数量仅在运行时已知,从您的评论看来,

It is like "%d %d %d ....", and varies its length dynamically.

只需多次调用 scanf 并使用适合读取单个值的格式字符串:

for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
scanf("%d", &image[i][j]);
}
}

这将与您的代码具有完全相同的语义(在它运行的平台上)。顺便说一下,添加一些错误处理(= 检查 scanf 的返回值),您的程序将在遇到无效值时停止读取,并继续使用 image< 中的未初始化值

如果性能有问题,只需放弃 scanf - 您可以通过手动编写标记化代码然后调用 strtol 轻松击败它;通过手动编写转换代码,您甚至可以比 strtol 更快(如果您不关心语言环境)。

在任何情况下,深入到汇编级别来构造对 scanf 的可变调用是一个糟糕的、不可移植的解决方案来寻找问题。

关于在 X64 gcc 内联 asm 中调用 scanf,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49723621/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com