において、
という発言をしたので、実際にできるかやってみました。みけCAT さんが書きました:リンカスクリプト(?)やnasmで出力に使用するメモリマップドな出力ポートやVRAMの要素をchar型の「変数」に割り当て、そこに代入することで出力や表示をすることはできる…?
自作の「OSっぽい何か」のMBR、BPB、 protected_load_with_api.asmを用いて、
C言語のコンパイル結果をロードし、実行します。
今回のコンパイル結果はたまたま__main関数のプログラムが.textセクションの最初に来ており、ヒープ領域も使用されなかったので、
.textセクションの中身を適当なファイルにコピペしてPROGRAM.BINとしてディスクのルートに置くことで読み込み、うまく実行することができました。
まずは、char型の変数を用いて1文字表示してみます。
VRAMに文字とアトリビュート(文字の色と背景の色)を書き込みます。
hoge.asm fuga.c Makefile
fuga_exe.txt: fuga.exe
objdump -d fuga.exe > fuga_ext.txt
fuga.exe: fuga.o hoge.o
gcc -nostdlib -static -o fuga.exe fuga.o hoge.o
fuga.o: fuga.c
gcc -c -O2 -o fuga.o fuga.c
hoge.o: hoge.asm
nasm -f elf32 -o hoge.o hoge.asm
せっかくなのでカラフルにしてみました。
また、カーソルを動かす関数をnasm側に書いてみました。
hoge.asm
bits 32
section .text
global move_cursor, _move_cursor
; void move_cursor(int x, int y)
move_cursor:
_move_cursor:
push ebp
mov ebp, esp
push ebx
; determine I/O address
mov dx, 0x03cc
in al, dx
xor cx, cx
test al, 1
jz not_d
mov cx, 0x0020
not_d:
; calculate address
mov eax, [ebp + 12]
mov ebx, 80
mul ebx
add eax, [ebp + 8]
mov ebx, eax
; write cursor location
pushf
cli
mov dx, 0x03b4 ; index of Cursor Location High
add dx, cx
mov al, 0x0e
out dx, al
mov dx, 0x03b5 ; data of Cursor Location High
add dx, cx
mov al, bh
out dx, al
mov dx, 0x03b4 ; index of Cursor Location Low
add dx, cx
mov al, 0x0f
out dx, al
mov dx, 0x03b5 ; data of Cursor Location Low
add dx, cx
mov al, bl
out dx, al
popf
pop ebx
leave
ret
absolute 0xb8000
global vram, _vram
vram:
_vram:
resb 2 * 80 * 25
extern volatile char vram[];
extern void move_cursor(int x, int y);
void __main(void) {
char hello[] = "hello, world!";
int i;
for(i = 0; hello[i] != '\0'; i++) {
vram[i * 2] = hello[i];
vram[i * 2 + 1] = (i % 6) + 9;
}
move_cursor(i, 0);
for(;;);
}
実行結果
オフトピック
今回はたまたまうまく動きましたが、このC言語のコードは識別子がアンダースコア2個から始まる関数を定義しているので、undefined behaviorになりそうです。
しかし、
Using and Porting the GNU Compiler Collection (GCC) - GCCコマンド・オプション
にあるように-lgccを指定し、void __main(void)をint main(void)に変えてビルドしたところatexitが無いというエラーが出たので、 という定義を追加したところ、
このatexit関数が.textセクションの先頭(0x401000)に来てしまい、しかもヘッダに書かれているエントリポイントも0x401000が指定されているため、うまく動かなそうでした。
nasmで__main関数にあたるものを定義し、main関数を呼び出すようにするといいかもしれない…と思いましたが、
同様に__mainが先頭に来ないのにエントリポイント情報が先頭を指してしまい、ダメでした。
どうすればいいのでしょうか…と言いたいところですが、ここで質問をしてしまうとStack Overflowで質問をするときにマルチポストになってしまい、
めんどくさいかもしれないので、このofftopicの内容は質問ではありません。
【追記】
識別子の先頭にアンダースコアが付くか付かないかを決め打ちにした場合、
エントリポイントをmain以外の名前にした上で(こうすると__mainへの依存が切れる)、
-eオプションを使用してエントリポイントのシンボルを指定するとよさそうです。
しかし、
Using and Porting the GNU Compiler Collection (GCC) - GCCコマンド・オプション
にあるように-lgccを指定し、void __main(void)をint main(void)に変えてビルドしたところatexitが無いというエラーが出たので、 という定義を追加したところ、
このatexit関数が.textセクションの先頭(0x401000)に来てしまい、しかもヘッダに書かれているエントリポイントも0x401000が指定されているため、うまく動かなそうでした。
nasmで__main関数にあたるものを定義し、main関数を呼び出すようにするといいかもしれない…と思いましたが、
同様に__mainが先頭に来ないのにエントリポイント情報が先頭を指してしまい、ダメでした。
どうすればいいのでしょうか…と言いたいところですが、ここで質問をしてしまうとStack Overflowで質問をするときにマルチポストになってしまい、
めんどくさいかもしれないので、このofftopicの内容は質問ではありません。
【追記】
識別子の先頭にアンダースコアが付くか付かないかを決め打ちにした場合、
エントリポイントをmain以外の名前にした上で(こうすると__mainへの依存が切れる)、
-eオプションを使用してエントリポイントのシンボルを指定するとよさそうです。