ようやく HDD を換えられた。 買ったのは Seagate の ST3120026A (ATA 120GB)。 二つあった HDD を一つにまとめられてスッキリした。
しかし、Windows だとどんなにでかい HDD でも 平気で一つのパーティションにするんだけど、 Linux だとなぜか細かく区切らないと気がすまないんだよな。
~ % df -h Filesystem Size Used Avail Use% Mounted on /dev/hda5 1.7G 88M 1.5G 6% / none 64M 4.0k 63M 1% /tmp /dev/hda6 16G 2.7G 13G 17% /usr /dev/hda7 18G 7.4G 10G 43% /home /dev/hda8 64G 9.7G 51G 16% /d /dev/hda9 9.2G 2.6G 6.1G 30% /var/backups /dev/hda1 99M 3.5M 90M 4% /boot
げ、/home が小さすぎたか。
(01:51)
Solaris には pmap てコマンドがあって、 プロセスのメモリマップを表示してくれる。 これがいいなーと思ってたんだけど、 Linux の procps をアップデートしたら pmap が入っていた。 そこでさっそくいろいろ実行してみる。 ついでにいろいろなアドレスも表示しとく。
~ % ./src/show-vmmap extern global = 08049bf8 static global = 08049bf4 function static = 08049bf0 function local = bffff608 malloc mem = 40140010 alloca mem = bffff484 main() = 080486d0 printf() = 080485a0 dl sin() = 40251360 1893: ./src/show-vmmap 08048000 4K r-x-- /show-vmmap ← printf() 08049000 4K rw--- /show-vmmap 40000000 76K r-x-- /ld-2.2.5.so 40013000 4K rw--- /ld-2.2.5.so 40014000 4K rw--- [ anon ] 4001b000 8K r-x-- /libdl-2.2.5.so 4001d000 4K rw--- /libdl-2.2.5.so 4001e000 1116K r-x-- /libc-2.2.5.so 40135000 24K rw--- /libc-2.2.5.so 4013b000 1048K rw--- [ anon ] 40248000 128K r-x-- /libm-2.2.5.so 40268000 4K rw--- /libm-2.2.5.so bfffe000 8K rwx-- [ stack ] total 2432K
まあだいたいは明らかっていうかあたりまえなんだけど、 興味を引かれたのが printf のアドレス。 よく見ると libc ではなくて show-vmmap の テキスト領域にあることになっている。
それでは Linux/Alpha はどうかと言うと、
dpw600au:~ % ./show-vmmap extern global = 0000000120011278 static global = 0000000120011274 function static = 0000000120011270 function local = 000000011ffff5f0 malloc mem = 00000200001d8018 alloca mem = 000000011ffff5b8 main() = 0000000120000960 printf() = 00000200000bcb60 dl sin() = 0000020000303e80 (pmapがなかったんで/proc/XXX/mapsから) 000000011fffe000-0000000120000000 rwxp 0000000120000000-0000000120002000 r-xp /home/aamine/c/linuxintro/src/show-vmmap 0000000120010000-0000000120012000 rwxp /home/aamine/c/linuxintro/src/show-vmmap 0000020000000000-000002000001e000 r-xp /lib/ld-2.2.5.so 000002000001e000-0000020000022000 rw-p 000002000002c000-000002000002e000 rwxp /lib/ld-2.2.5.so 000002000002e000-0000020000032000 r-xp /lib/libdl-2.2.5.so 0000020000032000-000002000003e000 ---p /lib/libdl-2.2.5.so 000002000003e000-0000020000042000 rwxp /lib/libdl-2.2.5.so 0000020000042000-00000200001b4000 r-xp /lib/libc-2.2.5.so ← ここ 00000200001b4000-00000200001c2000 ---p /lib/libc-2.2.5.so 00000200001c2000-00000200001d2000 rwxp /lib/libc-2.2.5.so 00000200001d2000-00000200001d8000 rwxp 00000200001d8000-00000200002da000 rw-p 00000200002da000-0000020000364000 r-xp /lib/libm-2.2.5.so 0000020000364000-000002000036a000 ---p /lib/libm-2.2.5.so 000002000036a000-0000020000378000 rwxp /lib/libm-2.2.5.so
libc のテキスト領域にあるように見える。普通だ。 もしかして位置独立だとこうなるのかなー (Alpha はデフォルトで PIC だから) と思って i686 のほうを -fPIC でコンパイルしてみたんだけど、 変化はないようだ。
ふーん。何かスタブが入ってるのかねえ。 OK, ディスアセンブルだ。
% objdump -d show-vmmap (略) 080484f0 <.plt>: 80484f0: ff 35 90 9b 04 08 pushl 0x8049b90 80484f6: ff 25 94 9b 04 08 jmp *0x8049b94 80484fc: 00 00 add %al,(%eax) 80484fe: 00 00 add %al,(%eax) 8048500: ff 25 98 9b 04 08 jmp *0x8049b98 8048506: 68 00 00 00 00 push $0x0 804850b: e9 e0 ff ff ff jmp 80484f0 <_init+0x28> 8048510: ff 25 9c 9b 04 08 jmp *0x8049b9c 8048516: 68 08 00 00 00 push $0x8 804851b: e9 d0 ff ff ff jmp 80484f0 <_init+0x28> 8048520: ff 25 a0 9b 04 08 jmp *0x8049ba0 8048526: 68 10 00 00 00 push $0x10 804852b: e9 c0 ff ff ff jmp 80484f0 <_init+0x28> 8048530: ff 25 a4 9b 04 08 jmp *0x8049ba4 (略) 8048596: 68 48 00 00 00 push $0x48 804859b: e9 50 ff ff ff jmp 80484f0 <_init+0x28> 80485a0: ff 25 c0 9b 04 08 jmp *0x8049bc0 ← ここに飛ぶ 80485a6: 68 50 00 00 00 push $0x50 80485ab: e9 40 ff ff ff jmp 80484f0 <_init+0x28> 80485b0: ff 25 c4 9b 04 08 jmp *0x8049bc4 80485b6: 68 58 00 00 00 push $0x58
printf は 080485a0 なので、いきなり jmp 命令か。 えーと、
jmp *0x8049bc0
って何だろ。あ、そうか、 「ポインタ 0x8049bc0 をデリファレンスしたアドレスに飛ぶ」だな。 0x8049bc0 は show-vmmap の BSS 領域だから、 ld.so がここに printf 本体のアドレスを置いてるんだろう。 OK, 自力で printf 本体をゲットするぜ。
こんな感じかな?
~ % cat x86resolve.c #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { typedef int (*printf_t)(const char *fmt, ...); printf_t myprintf; char *p; p = (char*)printf; p += 2; /* skip instruction */ printf("BSS address = %08lx\n", (unsigned long)(*(long*)p)); myprintf = (printf_t)(*(long*)(*(long*)p)); printf("printf() body = %08lx\n", (unsigned long)myprintf); myprintf("test %s\n", "OK"); exit(0); } ~ % ./x86resolve BSS address = 08049648 printf() body = 4006f8ec test OK
おおっ。うまくいってしまった!
というか .plt って何の略やねん。 …… ld.so(1) によると Procedure Linkage Table らしい。 なるほどね。
このへんまで書いたところでずっと詳しいページを見付けた。
こうなると問題にすべきは 「なんで x86 では libc のテキスト領域を指していないか」 じゃなくて、 「なんで Alpha では libc のテキスト領域を指しているか」 のような気がするな。
……
そうか。 Alpha は最初からグローバルポインタテーブルを使ってるせいだ。 もともと間接参照してるんだからもう一段入れる必要はない。
(01:57)
なぜわたしは突然 Alpha アセンブラを書き始めているのでしょうか
.file 1 "put.c" .set noat .set noreorder .section .rodata $LC0: .ascii "Hello, World!\12\0" .text .align 5 .globl main .ent main main: .mask 0x4000000,-16 ldah $29,9($27) lda $29,4896($29) $main..ng: lda $30,-16($30) lda $17,$LC0 lda $16,1 lda $18,15 lda $0,4 callsys stq $26,0($30) .prologue 1 mov $31,$16 lda $16,3 lda $0,1 ldq $26,0($30) nop lda $30,16($30) ret $31,($26),1 .end main
だめだ落ちる……。 とりあえず gcc のインラインアセンブラから始めて _exit(2) は呼べるようになったんだけど、 write(2) を呼ぼうとした瞬間に落ちた。 gp (global pointer) にまともな値が入ってないから、 リテラル (BSS) を参照した瞬間に落ちるようだ。
くそう、BSS がだめならスタックに積んでくれるわ!
~ % cat put.s .set noreorder .text .globl main .ent main main: lda $16,10 ; a0 ← '\n' stb $16,0($30) ; push a0 lda $16,97 ; a0 ← 'a' stb $16,-1($30) ; push a0 subq $30,1,$17 ; a1 ← sp lda $16,1 ; a0 ← 1 (STDOUT_FILENO) lda $18,2 ; a2 ← 2 lda $0,4 ; v0 ← 4 (NR_write) callsys lda $16,7 ; a0 ← 7 lda $0,1 ; v0 ← 1 (NR_exit) callsys .end main ~ % gcc -static -nostdlib -Wl,--entry=main put.s -o put ~ % ./put a ~ % echo $? 7
うごいた!
(02:59)
Copyright (c) 2002-2007 青木峰郎 / Minero Aoki. All rights reserved.