我如何 assemble GAS 汇编和 link 它与 Open Watcom C 库一起使用?

How do I assemble GAS assembly and link it with the Open Watcom C library?

我正在尝试生成 16 位 DOS 可执行文件,但使用的是 gcc 编译器。所以我使用的是古老的 gcc-4.3 ia16 端口。我制作了我的构建的 Docker 图像:https://registry.hub.docker.com/u/ysangkok/ia16-gcc-rask

这是我正在尝试的:

host $ mkdir results
host $ docker run -v $PWD/results:/results -it ysangkok/ia16-gcc-rask
container $ cd results

我没有包含头文件,因为 gcc 不能使用 OpenWatcom 的 libc 头文件。

container $ echo 'main() { printf("lol"); }' > test.c

我没有 link 因为我没有可用的 16 位 binutils。如果我构建一个目标文件,它没有正确标记为 16 位。

container $ /trunk/build-ia16-master/prefix/bin/ia16-unknown-elf-gcc -S test.c

现在我有了这个程序集文件:

    .arch i8086,jumps
    .code16
    .att_syntax prefix
#NO_APP
    .section    .rodata
.LC0:
    .string "lol"
    .text
    .p2align    1
    .global main
    .type   main, @function
main:
    pushw   %bp
    movw    %sp,    %bp
    subw    , %sp
    call    __main
    movw    $.LC0,  %ax
    pushw   %ax
    call    printf
    addw    , %sp
    movw    %bp,    %sp
    popw    %bp
    ret
    .size   main, .-main
    .ident  "GCC: (GNU) 4.3.0 20070829 (experimental)"

在容器外部,在主机中,我尝试使用 yasm assemble 它:

 % yasm -m x86 -p gas -f elf -o test.o test.s  
test.s:1: warning: directive `.arch' not recognized
test.s:3: error: junk at end of line, first unrecognized character is `p'

由于yasm不理解,我把语法行注释掉了,再试一次,这次成功了。

我测试重定位符号:

 % objdump -r test.o

test.o:     file format elf32-i386

RELOCATION RECORDS FOR [.text]:
OFFSET   TYPE              VALUE 
00000007 R_386_PC16        __main
0000000a R_386_16          .rodata
0000000e R_386_PC16        printf

遗憾的是它们是 32 位的。当我在容器中尝试 link 时,它不起作用:

root@1341f35c4590:/# cd ow/binl/
root@1341f35c4590:/ow/binl# WATCOM=/ow /ow/binl/wlink 
Open Watcom Linker Version 1.9
Portions Copyright (c) 1985-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.
See http://www.openwatcom.org/ for details.
Press CTRL/D to finish
WLINK>system dos
WLINK>file /results/test.o
[ comment: i press control-d on the next line ]
WLINK>loading object files
Warning! W1080: file /results/test.o is a 32-bit object file
Error! E2015: file /results/test.o(test.s): bad relocation type specified
Error! E2015: file /results/test.o(test.s): bad relocation type specified
Error! E2015: file /results/test.o(test.s): bad relocation type specified

如果我尝试制作 COFF 而不是 ELF,yasm 甚至不能 assemble:

root@1341f35c4590:/# cd ow/binl/
root@1341f35c4590:/ow/binl# WATCOM=/ow /ow/binl/wlink 
Open Watcom Linker Version 1.9
Portions Copyright (c) 1985-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.
See http://www.openwatcom.org/ for details.
Press CTRL/D to finish
WLINK>system dos
WLINK>file /results/test.o
WLINK>loading object files
Warning! W1080: file /results/test.o is a 32-bit object file
Error! E2015: file /results/test.o(test.s): bad relocation type specified
Error! E2015: file /results/test.o(test.s): bad relocation type specified
Error! E2015: file /results/test.o(test.s): bad relocation type specified

我知道 yasm 不支持 16 位,但也许有解决方法?是否有兼容 GAS 的 16 位 assembler? GAS-to-Intel 转换器不工作。

不是专家,但据我所知,没有 16 位 GAS 兼容的汇编器。

此外,gcc 从未打算生成 8086 16 位代码。 Rask 端口在某种意义上产生 16 位代码 默认情况下,操作数大小为 16 位。因此,像 mov ax, 1234h 这样的指令将作为 b8 34h 12h 而不是 66 b8 34h 12h 发出 在实模式下解释为 mov eax, xxxx1234h(如果你在 80386+ 上 运行)

地址模式也是如此。

问题是这只是代码,目标文件格式仍然是 32 位的,所以它们最终会被 32 位工具使用,最终用于 v86环境。 例如,ELF 不支持 16 位重定位,COFF 也不支持(根据 nasm)。

因此,即使 GCC 和 GAS 生成 16 位代码,它们也只输出相对较新的对象格式。 每个给定目标文件的工具都会创建 MZ 或 COM 可执行文件,这些工具是在这些格式之前创建的,并且不支持它们。 由于很久以前就不再使用 DOS,因此没有努力增加对新格式的支持。


非常长的解决方法(不打算使用)

我只能想象两个,非常非常难,使用 gcc 作为编译器的方法。

  1. 尝试移植到 NASM。 NASM 支持比 YASM 多得多的输出文件格式(再次旧的 16 位格式已被删除)。

Assemble 带有 -masm=intel 标志的源文件以获取 Intel 语法。那么你需要一个工具来将 GAS 点指令转换为 NASM 指令。 这必须手动编码。它们中的大多数都是简单的替换,例如 .global XXX 到 GLOBAL XXX 但您需要转换有效地址和 为未定义的函数添加 EXTERN XXX

  1. 自己搬迁。 (需要精通IA16架构和DOS)

您不得使用任何外部符号并生成 PIC 代码(-fPIC 标志)和原始二进制文件(即代码)。 定义一个函数指针结构,一个用于您需要使用的每个外部函数,例如

struct context_t
{
    int (*printf)(char* format, ...); 
    ...
};
然后声明一个指向context_t的指针,比如context_t* ctx; 如果您需要使用 printf 之类的函数,请改用 ctx->printf。 编译代码。

现在创建一个 C 源代码,将其称为加载器,它定义了一个 context_t 类型的变量并初始化其指针。 然后加载程序必须读取二进制文件,找到为 ctx 指针分配的 space 并将其设置为其 context_t 变量的地址,然后 将二进制文件加载到内存中(在段边界处)并通过远调用执行它。

需要找到指针在文件中的位置,可以使用GCC生成的映射文件(-Xlinker -Map=output.map开关)或者使用签名 像旧的 BIOS PCI 32 位服务($PCI 签名)并扫描它。 请注意,GCC 生成的代码可能会施加其他约束,但 PIC 开关应将其最小化。 您可以在加载程序之后追加二进制文件(请注意,如果您使用 MZ 格式并注意对齐)并简化事情。