gasでcrtを書く
前回のコードをgas(GNU assembler)で書き直した。また、以下の点が異なる。
- gasはmasmのような高級な構文(invokeや.ifなど)を持っていないので、全てより低水準な表現で書き直されている。
ループなどをアセンブラで書くのは面倒だったので、Cで書ける部分はCで書いた。(crt.c)
- 関数の戻り値をチェックしている。
- main()に渡されるargvの型を、wchar_t *argv[]ではなく、より標準的なchar *argv[]にした。
CommandLineToArgvW()で確保された、ワイド文字列の領域を、WideCharToMultiByte()でマルチバイト文字列に変換した後のargvで破壊的に更新することが合法なのかどうかは謎なのだが、呼び出し側の責任でLocalFree()することが義務付けられているようなので、問題無いと思う。
- MinGWのgcc 3.4.5では、main()のプロローグ部分で_allocaの呼び出しが要求されるので、適当に実装。
test.c
コンパイルは以下のように行う。
- gasはmasmのような高級な構文(invokeや.ifなど)を持っていないので、全てより低水準な表現で書き直されている。
ループなどをアセンブラで書くのは面倒だったので、Cで書ける部分はCで書いた。(crt.c)
- 関数の戻り値をチェックしている。
- main()に渡されるargvの型を、wchar_t *argv[]ではなく、より標準的なchar *argv[]にした。
CommandLineToArgvW()で確保された、ワイド文字列の領域を、WideCharToMultiByte()でマルチバイト文字列に変換した後のargvで破壊的に更新することが合法なのかどうかは謎なのだが、呼び出し側の責任でLocalFree()することが義務付けられているようなので、問題無いと思う。
- MinGWのgcc 3.4.5では、main()のプロローグ部分で_allocaの呼び出しが要求されるので、適当に実装。
test.c
extern void *stdout;
#define EOF (-1)
int main(int argc, char *argv[])
{
int i;
for (i = 0; i < argc; i++) {
if (fputs(argv[i], stdout) == EOF) return 1;
if (fputs("\n", stdout) == EOF) return 1;
}
return 0;
}
コンパイルは以下のように行う。
$ gcc -fno-builtin -c test.c
$ gcc -fno-builtin -c crt.c
$ as crt.s -o crt1.o
$ ld crt.o crt1.o test.o -lkernel32 -lshell32 --entry=mainCRTStartup
crt.c
crt.s
typedef unsigned short wchar_t;
char *strcpy(char *s1, char *s2)
{
char *p;
p = s1;
while (*s1++ = *s2++);
return p;
}
int ArgvWtoArgvA(int argc, wchar_t *argvW[])
{
int i;
char buf[1024];
for (i = 0; i < argc; i++) {
if (wcstombs(buf, argvW[i], 1024) == -1) return 1;
strcpy((char *)argvW[i], buf);
}
return 0;
}
crt.s
.intel_syntax noprefix
.equ STD_INPUT_HANDLE, (-10)
.equ STD_OUTPUT_HANDLE, (-11)
.equ STD_ERROR_HANDLE, (-12)
.equ EOF, (-1)
.equ NULL, 0
.equ CP_OEMCP, (932)
.data
.global _stdin
_stdin: .long 0
.global _stdout
_stdout: .long 0
.global _stderr
_stderr: .long 0
.text
.global _fputs
_fputs:
push ebp
mov ebp, esp
sub esp, 8
push [ebp+8]
call _lstrlenA@4
mov [ebp-4],eax
push NULL
lea eax, [ebp-8]
push eax
push [ebp-4]
push [ebp+8]
push [ebp+12]
call _WriteFile@20
cmp eax, 0
jne 1f
mov eax, EOF
1: mov esp, ebp
pop ebp
ret
.global _wcstombs
_wcstombs:
push ebp
mov ebp, esp
push 0
push 0
push [ebp+16]
push [ebp+8]
push -1 # null terminated
push [ebp+12]
push 0
push CP_OEMCP
call _WideCharToMultiByte@32
cmp eax, 0
jne 1f
mov eax, -1
1: mov esp, ebp
pop ebp
ret
.global mainCRTStartup
mainCRTStartup:
push ebp
mov ebp, esp
sub esp, 16
call _GetCommandLineW@0
cmp eax, NULL
jne 1f
jmp fatal_error
1: mov [ebp-4], eax
lea eax, [ebp-8]
push eax
push [ebp-4]
call _CommandLineToArgvW@8
cmp eax, NULL
jne 2f
jmp fatal_error
2: mov [ebp-12], eax
push eax
push [ebp-8]
call _ArgvWtoArgvA
add esp, 8
cmp eax, 0
je 3f
jmp fatal_error
3: push [ebp-12]
push [ebp-8]
call _main
add esp, 8
mov [ebp-16], eax
push [ebp-12]
call _LocalFree@4
cmp eax, 0
je 4f
jmp fatal_error
4: mov eax, [ebp-16]
jmp 5f
fatal_error:
mov eax, 2
5: push eax
call _ExitProcess@4
mov esp, ebp
pop ebp
ret
.global ___main
___main:
push STD_INPUT_HANDLE
call _GetStdHandle@4
mov _stdin, eax
push STD_OUTPUT_HANDLE
call _GetStdHandle@4
mov _stdout, eax
push STD_ERROR_HANDLE
call _GetStdHandle@4
mov _stderr, eax
ret
# for gcc 3.4.5
.global __alloca
__alloca: # void *_alloca(size_t size)
pop ecx # [esp] : return address
mov eax, [esp] # [esp+4] : size
add eax, 3 # 4 byte alignment
and eax, 0xfffffffc # (size + 3) & 0xfffffffc
sub esp, eax # allocate
lea eax, [esp+4] # return value
jmp ecx # ret
