リンカスクリプトにはいろいろな面白い機能がある。
今回はシンボルをリンカスクリプトで定義して、それをC言語側の関数から表示してみたい。
まず、リンカスクリプトはこれ。
SECTIONS{
. = 0x8048000 + SIZEOF_HEADERS;
.text : { stext = .; *(.text) etext = .; }
.rodata : { srodata = .; *(.rodata) erodata = .; }
.data : { sdata = .; *(.data) edata = .; }
.bss : { sbss = .; *(.bss) ebss = .; }
}
そして、main関数は次のようにした。(printというのはprintfの自作バージョン)
extern char stext[ ], etext[ ], srodata[ ], erodata[ ], sdata[ ], edata[ ], sbss[ ], ebss[ ];
int main(int argc, char **argv){
print("stext = %x, etext = %x\n", stext, etext);
print("srodata = %x, erodata = %x\n", srodata, erodata);
print("sdata = %x, edata = %x\n", sdata, edata);
print("sbss = %x, ebss = %x\n", sbss, ebss);
return 0;
}
実行結果は次のようになる。
stext = 80480F4, etext = 8048356
srodata = 8048356, erodata = 80483B8
sdata = 8048500, edata = 8048D00
sbss = 8048D90, ebss = 8048D90
readelf -aで調べてみると次のようになっている。
確かに各セクションのアドレスが表示されているようだ。
セクションヘッダ:
[番] 名前 タイプ アドレス Off サイズ ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 080480f4 0000f4 000262 00 AX 0 0 1
[ 2] .rodata PROGBITS 08048356 000356 000062 00 A 0 0 1
[ 3] .interp PROGBITS 080483b8 0003b8 000013 00 A 0 0 1
[ 4] .note.gnu.build-i NOTE 080483cc 0003cc 000024 00 A 0 0 4
[ 5] .dynsym DYNSYM 080483f0 0003f0 000010 10 A 6 1 4
[ 6] .dynstr STRTAB 08048400 000400 000001 00 A 0 0 1
[ 7] .gnu.hash GNU_HASH 08048404 000404 000018 04 A 5 0 4
[ 8] .eh_frame PROGBITS 0804841c 00041c 000044 00 A 0 0 4
[ 9] .rel.dyn REL 08048460 000460 0000a0 08 A 5 0 4
[10] .data PROGBITS 08048500 000500 000800 00 WA 0 0 1
[11] .dynamic DYNAMIC 08048d00 000d00 000090 08 WA 6 0 4
[12] .comment PROGBITS 00000000 000d90 00002b 01 MS 0 0 1
[13] .symtab SYMTAB 00000000 000dbc 000410 10 14 51 4
[14] .strtab STRTAB 00000000 0011cc 000257 00 0 0 1
[15] .shstrtab STRTAB 00000000 001423 000089 00 0 0
「extern char stext[ ]」などとしてわざわざ配列に見せかけているのは、printする際に&をつけなくてもいいようにである。
(逆にシンボルの指すアドレスにある値を書き換えようとすると*をつける必要があるが...)
もし「extern char stext」などとした場合は、printする際に&をつければよい。
また、実はリンカスクリプトの中での変数(に見えるもの)は、実際にはシンボルであって変数ではない。
なぜなら、実行時にメモリ上に確保されないからである。
さて、ここでextern宣言の際に「extern char stext[ ]」としているが、「extern char *stext」などとしていいのかを考えてみる。
答えは「否」である。
このあたりでポインタを理解できていない人は苦しむのだが、じっくり考えてみてほしい。
ポインタ変数は名前の通り「変数」であり、実際にメモリを消費する。
つまり、機械語レベルまで落とすと「変数」も「ポインタ変数」もただの値を記憶する変数である。
C言語レベルでは型チェックが働いて「char *p = 1」といった文は書けないが、コンパイルさえ通ってしまえば、アセンブラレベルではただの整数処理である。
まったく問題はない。(実際に動作するかは置いておいて)
つまり、「extern char *stext」などとした場合は「extern char stext」などとした場合と同様に、printする際に&を付けなければいけないことになる。
これならわざわざポインタ変数などは使わずに後者を使えばいいだろう。