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

1

6

I am trying to produce 16-bit DOS executables, but using the gcc compiler. So I am using the ancient gcc-4.3 ia16 port. I made a Docker image of my build: https://registry.hub.docker.com/u/ysangkok/ia16-gcc-rask

Here's what I am trying:

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

I don't include the header, cause gcc can't use OpenWatcom's libc headers.

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

I don't link cause I don't have 16-bit binutils available. If I build an object file, it isn't correctly marked as 16-bit.

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

Now I have this assembly file:

    .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)"

Outside the container, in the host, I try to assemble it with 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'

I comment out the syntax line since yasm doesn't understand it, and try again, this time it succeeds.

I test the relocation symbols:

 % 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

Sadly they are 32-bit. When I try and link anyway in the container, it doesn't work:

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

If I try and make a COFF instead of an ELF, yasm can't even 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

I know yasm doesn't support 16-bit, but maybe there is a workaround? Is there a GAS-compatible 16-bit assembler? The GAS-to-Intel converters are not working.

Reynold answered 12/5, 2015 at 12:32 Comment(4)
Any particular reason for not using OpenWatcom as the compiler instead? Does that old GCC port produce better code?Tacmahack
@Michael: Yes, the GCC output is optimized way better, this is why I want to use it.Reynold
It is difficult for me to believe that there is no support for 16-bit x86 targets in binutils...Hallowell
delorie.com/djgpp/16bit/gcc #228262 delorie.com/djgpp/16bitMacle
M
2

I'm not an expert but AFAIK there are no 16 bit GAS compatible assemblers.

Furthermore gcc was never meant to produce 8086 16 bit code. The Rask port produce 16 bit code in the sense that the operand size is 16 bit by default. So an instruction like mov ax, 1234h is emitted as b8 34h 12h rather than as 66 b8 34h 12h which will be interpreted as mov eax, xxxx1234h in real mode (if you run on 80386+)

Same thing for the address mode.

The problem is that this is just the code, the object file formats are still for 32 bit, so they are meant to be used by 32 bit tools eventually for use in a v86 environment. ELF for example don't support 16 bit relocation, nor COFF does (according to nasm).

So even GCC and GAS produce 16 bit code they output only relatively new object format. Every tools that given an object file create a MZ or COM executable was created before these formats and don't support them. No efforts have been spent on adding support to new formats as DOS ceased to be used long time ago.


Very long Workarounds (not meant to be used)

I can only image two, very very hard, way to use gcc as a compiler.

  1. Try porting to NASM. NASM support far more output file format than YASM (again old 16 bit format have been dropped).

Assemble the source file with -masm=intel flag to get Intel syntax. Then you need a tool to convert GAS dot directives to NASM directives. This have to be coded manually. Most of them are simple substitutions like .global XXX to GLOBAL XXX but you need to convert effective addresses and add EXTERN XXX for undefined functions.

  1. Do the relocations yourself. (You need to be skilled with IA16 architecture and DOS)

You must not use any external symbol and produce PIC code (-fPIC flag) and a raw binary (i.e. just code). Define a struct of function pointers, one for each external function you need to use, something like

struct context_t
{
    int (*printf)(char* format, ...); 
    ...
};
Then declare a pointer to context_t, say context_t* ctx; If you need to use a function like printf use ctx->printf instead. Compile the code.

Now create a C source, call it loader, that define a variable of type context_t and initialize its pointers. The loader then must read the binary file, locate the space allocated for the ctx pointer and set it to the address of its context_t variable, then load the binary file in memory (at segment boundary) and execute it with a far call.

You need to find the position of the pointer in the file, you can use a map file generated by GCC (-Xlinker -Map=output.map switch) or use a signature like the old BIOS PCI 32bit service (the $PCI signature) and scan for it. Beware that the code generated by GCC may impose other constraints, but the PIC switch should minimize this. You can event append the binary file after the loader (beware if you use MZ format and mind the alignment) and simplify things.

Marquez answered 21/5, 2015 at 16:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.