はじめに
@moriaiさんの書かれた『Rust で DOS プログラミングしてみる?』という記事を拝見し、いまどきのツールでリアルモードで動作するプログラムを作成をすることに興味を持ったのですが、Rust はなんもわからんので gcc で試してみることとしました。
環境
開発環境の OS やら gcc やらは以下の版のものを使用しています。
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=19.04
DISTRIB_CODENAME=disco
DISTRIB_DESCRIPTION="Ubuntu 19.04"
$ gcc --version
gcc (Ubuntu 8.3.0-6ubuntu1) 8.3.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ as --version
GNU assembler (GNU Binutils for Ubuntu) 2.32
Copyright (C) 2019 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or later.
This program has absolutely no warranty.
This assembler was configured for a target of `x86_64-linux-gnu'.
$ ld --version
GNU ld (GNU Binutils for Ubuntu) 2.32
Copyright (C) 2019 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or (at your option) a later version.
This program has absolutely no warranty.
$
あと実行環境として、Windows10Home 64bit 上で MS-DOS Player for Win32-x64 の i486-x64 版を使用しています。
書いたコード
CC = gcc
CFLAGS = \
-march=i386 -m16 \
-std=c11 \
-Wall -Wextra \
-fno-pic \
-fdata-sections -ffunction-sections \
-fno-align-functions \
-fno-builtin \
-flto \
-O2
AS = $(CC)
AFLAGS = $(CFLAGS)
LD = gcc
LFLAGS = \
-march=i386 -m16 \
-Tlink.ld \
-nostdlib -static \
-flto \
-Wl,--gc-sections \
-Wl,--build-id=none
PROG = hello.com
PROGX = $(PROG:.com=.x)
OBJS = start.o hello.o doscall.o
.SUFFIXES:
.SUFFIXES: .c .S .o
.c.o:
$(CC) $(CFLAGS) -c $< -o $@
.S.o:
$(AS) $(AFLAGS) -c $< -o $@
target: $(PROG)
clean:
rm -f $(OBJS) $(PROG) $(PROGX)
all: clean target
$(PROGX): $(OBJS)
$(LD) $(LFLAGS) $^ -o $@ -Wl,-Map=$*.map
$(PROG): $(PROGX)
objcopy $< -O binary $@
doscall.o: doscall.h
hello.o: doscall.h
OUTPUT_FORMAT(elf32-i386)
ENTRY(_start)
SECTIONS
{
. = 0x100;
.text : {
*(.start)
*(.text) *(.text.*)
}
.data : {
*(.data) *(.data.*)
*(.rodata) *(.rodata.*)
*(.gcc_except_table)
}
.bss (NOLOAD) : {
*(.bss) *(.bss.*)
*(COMMON)
}
/DISCARD/ : { *(.*) }
}
.code16gcc
.text
.section ".start", "ax"
.globl _start
_start:
call main
.code16
ret
.end
#ifndef DOSCALL_H
#define DOSCALL_H
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
void dos_write(uint16_t fd, const void* buf, uint16_t count);
_Noreturn void dos_exit(uint8_t status);
#endif/*DOSCALL_H*/
#include "doscall.h"
void dos_write(uint16_t fd, const void* buf, uint16_t count)
{
__asm __volatile(
"mov $0x40, %%ah \n"
"int $0x21 \n"
:
: "b"(fd), "c"(count), "d"(buf)
: "eax"
);
}
_Noreturn void dos_exit(uint8_t status)
{
__asm __volatile(
"mov $0x4c, %%ah \n"
"int $0x21 \n"
:
: "a"(status)
);
__builtin_unreachable();
}
int main(void)
{
const uint16_t stdout = 1;
static const char __attribute__((aligned(1))) msg[14] = "Hello, World\r\n";
dos_write(stdout, msg, sizeof(msg));
dos_exit(0);
}
これを上記環境で make することで 52バイトの hello.com が作成されました。
$ make
gcc -march=i386 -m16 -std=c11 -Wall -Wextra -fno-pic -fdata-sections -ffunction-sections -fno-align-functions -fno-builtin -flto -O2 -c start.S -o start.o
gcc -march=i386 -m16 -std=c11 -Wall -Wextra -fno-pic -fdata-sections -ffunction-sections -fno-align-functions -fno-builtin -flto -O2 -c hello.c -o hello.o
gcc -march=i386 -m16 -std=c11 -Wall -Wextra -fno-pic -fdata-sections -ffunction-sections -fno-align-functions -fno-builtin -flto -O2 -c doscall.c -o doscall.o
gcc -march=i386 -m16 -Tlink.ld -nostdlib -static -flto -Wl,--gc-sections -Wl,--build-id=none start.o hello.o doscall.o -o hello.x -Wl,-Map=.map
objcopy hello.x -O binary hello.com
$ ls -l hello.com
-rwxr-xr-x 1 fujita fujita 52 Jan 12 00:00 hello.com
$
MS-DOS Player を使用して動作させてみると
$ msdos hello.com
Hello, World
$
動作しました。
コード評価
hello.com の内容を見てみると
$ hexdump -C hello.com
00000000 66 e8 01 00 00 00 c3 66 53 66 bb 01 00 00 00 66 |f......fSf.....f|
00000010 b9 0e 00 00 00 66 ba 26 01 00 00 b4 40 cd 21 66 |.....f.&....@.!f|
00000020 31 c0 b4 4c cd 21 48 65 6c 6c 6f 2c 20 57 6f 72 |1..L.!Hello, Wor|
00000030 6c 64 0d 0a |ld..|
00000034
$ objdump -b elf32-i386 -m i8086 -d hello.x
hello.x: file format elf32-i386
Disassembly of section .text:
00000100 <_start>:
100: 66 e8 01 00 00 00 calll 107 <main>
106: c3 ret
00000107 <main>:
107: 66 53 push %ebx
109: 66 bb 01 00 00 00 mov $0x1,%ebx
10f: 66 b9 0e 00 00 00 mov $0xe,%ecx
115: 66 ba 26 01 00 00 mov $0x126,%edx
11b: b4 40 mov $0x40,%ah
11d: cd 21 int $0x21
11f: 66 31 c0 xor %eax,%eax
122: b4 4c mov $0x4c,%ah
124: cd 21 int $0x21
$
無駄な push %ebx が生成されてしまっている点は残念です。また、システムコールのパラメタの設定に 16ビットレジスタで済むところを 32ビットレジスタを使用してしまい冗長なコードとなってしまっている印象です。リンク時最適化が働いてシステムコールの関数呼び出しがインライン展開されてる点は良い感じですね。
いまどきはリアルモード用のプログラムを書く機会もそうそうないとは思いますが、OS のブートローダーを書いたりする程度の用途には使えるかもしれません。
おわりに
おわりです。