Since you are using a native compiler and not an i686(or i386) cross compiler you can get a fair amount of extra information. It is rather dependent on the compiler configurations. I would recommend doing the following to remove unwanted code generation and sections:
- Use GCC option
-fno-asynchronous-unwind-tables
to eliminate any .eh_frame
sections. This is the cause of the unwanted data appended at the end of your DOS COM program in this case.
- Use GCC option
-static
to build without relocations to avoid any form of dynamic linking.
- Have GCC pass the
--build-id=none
option to the linker with -Wl
to avoid unnecessarily generating any .note.gnu.build-id
sections.
- Modify the linker script to DISCARD any
.comment
sections.
Your build command could look like:
gcc -fno-pie -static -Os -nostdlib -fno-asynchronous-unwind-tables -ffreestanding \
-m16 -march=i386 -Wl,--build-id=none,--nmagic,--script=simple_dos.ld simple_dos.c \
-o simple_dos.com
I would modify your linker script to look like:
OUTPUT_FORMAT(binary)
SECTIONS
{
. = 0x0100;
.text :
{
*(.text*);
}
.data :
{
*(.data);
*(.rodata*);
*(.bss);
*(COMMON)
}
_heap = ALIGN(4);
/DISCARD/ : { *(.comment); }
}
Besides adding a /DISCARD/ directive to eliminate any .comment
sections I also add *(COMMON)
along side .bss
. Both are BSS sections. I have also moved them after the data sections as they won't take up space in the .COM file if they appear after the other sections. I also changed *(.rodata);
to *(.rodata*);
and *(.text);
to *(.text*);
because GCC can generate section names that begin with .rodata
and .text
but have different suffixes on them.
Inline Assembly
Not related to the problem you asked about, but is important. In this inline assembly:
__asm__(
"mov $0x09, %%ah;"
"int $0x21;"
: // no output
: "d"(str)
: "ah"
);
Int 21h/AH=9h also clobbers AL. You should use ax
as the clobber.
Since you are passing the address of an array through a register you will also want to add a memory
clobber so that the compiler realizes the entire array into memory before your inline assembly is emitted. The constraint "d"(str)
only tells the compiler that you will be using the pointer as input, not what the pointer points at.
Likely if you compiled with optimisations at -O3
you'd probably discover the following version of the program doesn't even have your string "Hello world$"
in it because of this bug:
__asm__(
".code16gcc;"
"call dosmain;"
"mov $0x4C, %AH;"
"int $0x21;"
);
void print(char *str)
{
__asm__(
"mov $0x09, %%ah;"
"int $0x21;"
: // no output
: "d"(str)
: "ax");
}
void dosmain()
{
char hello[] = "Hello world$";
print(hello);
}
The generated code for dosmain
allocated space on the stack for the string but never put the string on the stack before printing the string:
00000100 <print-0xc>:
100: 66 e8 12 00 00 00 calll 118 <dosmain>
106: b4 4c mov $0x4c,%ah
108: cd 21 int $0x21
10a: 66 90 xchg %eax,%eax
0000010c <print>:
10c: 67 66 8b 54 24 04 mov 0x4(%esp),%edx
112: b4 09 mov $0x9,%ah
114: cd 21 int $0x21
116: 66 c3 retl
00000118 <dosmain>:
118: 66 83 ec 10 sub $0x10,%esp
11c: 67 66 8d 54 24 03 lea 0x3(%esp),%edx
122: b4 09 mov $0x9,%ah
124: cd 21 int $0x21
126: 66 83 c4 10 add $0x10,%esp
12a: 66 c3 retl
If you change the inline assembly to include a "memory"
clobber like this:
void print(char *str)
{
__asm__(
"mov $0x09, %%ah;"
"int $0x21;"
: // no output
: "d"(str)
: "ax", "memory");
}
The generated code may look similar to this:
00000100 <print-0xc>:
100: 66 e8 12 00 00 00 calll 118 <dosmain>
106: b4 4c mov $0x4c,%ah
108: cd 21 int $0x21
10a: 66 90 xchg %eax,%eax
0000010c <print>:
10c: 67 66 8b 54 24 04 mov 0x4(%esp),%edx
112: b4 09 mov $0x9,%ah
114: cd 21 int $0x21
116: 66 c3 retl
00000118 <dosmain>:
118: 66 57 push %edi
11a: 66 56 push %esi
11c: 66 83 ec 10 sub $0x10,%esp
120: 67 66 8d 7c 24 03 lea 0x3(%esp),%edi
126: 66 be 48 01 00 00 mov $0x148,%esi
12c: 66 b9 0d 00 00 00 mov $0xd,%ecx
132: f3 a4 rep movsb %ds:(%si),%es:(%di)
134: 67 66 8d 54 24 03 lea 0x3(%esp),%edx
13a: b4 09 mov $0x9,%ah
13c: cd 21 int $0x21
13e: 66 83 c4 10 add $0x10,%esp
142: 66 5e pop %esi
144: 66 5f pop %edi
146: 66 c3 retl
Disassembly of section .rodata.str1.1:
00000148 <_heap-0x10>:
148: 48 dec %ax
149: 65 6c gs insb (%dx),%es:(%di)
14b: 6c insb (%dx),%es:(%di)
14c: 6f outsw %ds:(%si),(%dx)
14d: 20 77 6f and %dh,0x6f(%bx)
150: 72 6c jb 1be <_heap+0x66>
152: 64 24 00 fs and $0x0,%al
An alternate version of the inline assembly that passes the sub function 9 via an a
constraint using a variable and marks it as an input/output with +
(since the return value of AX gets clobbered) could be done this way:
void print(char *str)
{
unsigned short int write_fun = (0x09<<8) | 0x00;
__asm__ __volatile__ (
"int $0x21;"
: "+a"(write_fun)
: "d"(str)
: "memory"
);
}
Recommendation: Don't use GCC for 16-bit code generation. The inline assembly is difficult to get right and you will probably be using a fair amount of it for low level routines. You could look at Smaller C, Bruce's C compiler, or Openwatcom C as alternatives. All of them can generate DOS COM programs.