我该如何组装GAS汇编代码并与Open Watcom C库进行链接?

6
我正在尝试使用gcc编译器生成16位DOS可执行文件。因此,我正在使用古老的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

我无法进行链接,因为我没有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    $4, %sp
    call    __main
    movw    $.LC0,  %ax
    pushw   %ax
    call    printf
    addw    $2, %sp
    movw    %bp,    %sp
    popw    %bp
    ret
    .size   main, .-main
    .ident  "GCC: (GNU) 4.3.0 20070829 (experimental)"

在容器之外,在宿主机上,我尝试使用yasm进行组装:

 % 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位的。当我尝试在容器中任意连接时,它不起作用:

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甚至无法进行汇编:
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位汇编器?GAS转Intel的转换器无法使用。


1
不使用OpenWatcom编译器的特别原因是什么?那个旧的GCC端口能产生更好的代码吗? - Michael
1
@Michael:是的,GCC的输出优化得更好,这就是我想使用它的原因。 - Janus Troelsen
我很难相信binutils没有对16位x86目标提供支持... - Laszlo Valko
http://www.delorie.com/djgpp/16bit/gcc/ https://dev59.com/tnVC5IYBdhLWcg3wpSzf http://www.delorie.com/djgpp/16bit/ - Prof. Falken
1个回答

2
我不是专家,但据我所知,没有16位GAS兼容汇编器。
此外,gcc从未旨在生成8086 16位代码。Rask端口默认情况下生成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作为编译器。
尝试移植到NASM。 NASM支持比YASM更多的输出文件格式(再次,旧的16位格式已被删除)。
使用-masm=intel标志汇编源文件以获取Intel语法。然后,您需要一个工具将GAS点指令转换为NASM指令。 这必须手动编码。它们中的大多数都是简单的替换,如.global XXX到GLOBAL XXX,但您需要转换有效地址并添加EXTERN XXX以获取未定义的函数。
自己做重定位。(您需要熟悉IA16体系结构和DOS)
您不能使用任何外部符号并生成PIC代码(-fPIC标志)和原始二进制文件(即只有代码)。 定义一个函数指针的结构,每个外部函数都需要一个,类似于
struct context_t
{
    int (*printf)(char* format, ...); 
    ...
};
然后声明一个指向context_t的指针,例如context_t* ctx; 如果需要使用像printf这样的函数,请改用ctx->printf。 编译代码。
现在创建一个C源文件,命名为loader,定义一个类型为context_t的变量并初始化其指针。 loader必须读取二进制文件,找到为ctx指针分配的空间,并将其设置为其context_t变量的地址,然后将二进制文件在内存中加载(在段边界处),并使用远程调用执行它。
您需要在文件中找到指针的位置,可以使用由GCC生成的地图文件(-Xlinker -Map=output.map开关),或者使用类似旧BIOS PCI 32位服务的签名($PCI签名)并进行扫描。请注意,GCC生成的代码可能会施加其他限制,但PIC开关应该将此最小化。您可以在loader之后附加二进制文件(如果使用MZ格式,请注意对齐),并简化事情。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接