0

我正在用 C++ 编写一些 16 位(双关语)代码,用 G++ 编译它。更多关于我在这里编译的上下文:强制 GCC 在调用函数之前将参数推送到堆栈上(使用 PUSH 指令)

我现在面临的问题是尝试链接我的目标文件时抛出的错误 LD。具体来说,这是一个代码情况:

asm(".code16gcc\n");
void f(const char*);
int main(){
    f("A constant string put in section .rodata at link-time");
}
void f(const char* s){ }

在汇编代码中,使用 -S 和 -mno-accumulate-outgoing-args 选项,G++ 会将其转换为(仅编写程序集的相关部分):

/APP
    .code16gcc

    .section    .rodata
.LC0:
    .string "A constant string put in section .rodata at link-time"
main:
.LFB0:
    /* here would be main's prologue, not put because it ain't relevant */

    // THIS IS THE CALL f("A constant string put in section .rodata at link-time");
    push    OFFSET FLAT:.LC0
    call    _Z1fPKc

这个应用程序是我正在开发的操作系统的一部分。具体来说,引导加载程序在 BIOS 内存中的地址 0x70D00 加载此代码。这使得 .rodata 的地址大于 0x70D00。由于 GCC 没有对纯 16 位代码的内置支持,它不知道执行“push OFFSET FLAT:.LC0”将意味着在纯 16 位情况下推送一个字。这意味着,如果 .rodata 的地址是 - 比如说 - 0x70DAA,那么指令将是“推送 0x70DAA”。这就是链接器抛出错误的原因:

在函数main': relocation truncated to fit: R_386_16 against.rodata'

-- 因为链接器知道 0x70DAA 不适合一个字。解决问题的方法是要求 GCC 在推送它们之前将参数移动到寄存器中。就像是:

/APP
    .code16gcc

    .section    .rodata
.LC0:
    .string "A constant string put in section .rodata at link-time"
main:
.LFB0:
    /* here would be main's prologue, not put because it ain't relevant */

    // THIS IS THE CALL f("A constant string put in section .rodata at link-time"); , now using EAX before pushing the string literal's offset in .rodata
    mov eax, OFFSET FLAT:.LC0 // move in eax instead
    push    eax // and push eax!
    call    _Z1fPKc

这就是MSVC在某些情况下所做的优化。我想知道是否有一种方法可以强制 GCC 做同样的事情......一个显然可行的替代方法是将属性((regparm(N))) 与函数 f 相关联。但这并不是一个很好的选择,因为它并没有真正将寄存器推入堆栈,而不是直接在 f 中使用它们 - 并且不能对任何函数执行此操作。您可以通过简短的 google 搜索了解更多信息,如果需要,我将在此处准确发布此选项的作用以及为什么它不会真正起作用,但这个问题帖子开始变得太长。

简而言之,我的问题是:我可以要求 GCC 将传递给函数的参数移动到寄存器中,然后再推送它们吗?

提前致谢!

4

1 回答 1

0

我已经想到了解决这个问题的方法,尽管我更喜欢 MOV-to-REG-and-PUSH 之类的方法。我想到的是,这只发生在编译器可以在编译时计算的地址上,比如放在.rodata中的字符串的地址。

知道了这一点,我在 main 中创建了一个局部变量,并将其用作传递的参数,如下所示:

asm(".code16gcc\n");
void f(const char*);
int main(){
    const char* s = "A constant string put in section .rodata at link-time";
    // Now use 's' as the argument instead of the string literal
    f(s);
}
void f(const char* s){ }

这有效地将生成的汇编代码更改为:

/APP
    .code16gcc

    .section    .rodata
.LC0:
    .string "A constant string put in section .rodata at link-time"
main:
.LFB0:
    /* here would be main's prologue, not put because it ain't relevant */

    // THIS IS THE CALL f(s);
    mov DWORD PTR [ebp-12], OFFSET FLAT:.LC0 // now specifically loaded in the DWORD 's'
    sub esp, 12
    push DWORD PTR [ebp-12]
    call    _Z1fPKc

可以看出,现在改为使用局部变量,字符串文字(在 .rodata 中)的地址专门以 DWORD 格式传输。这有效地避免了链接器错误,尽管它使用了一些可忽略的额外堆栈空间。

于 2012-11-29T22:55:37.153 回答