web-dev-qa-db-ja.com

単純なリンカースクリプトを正しく使用するにはどうすればよいですか?実行可能ファイルは、実行時にSIGKILLを取得します

より深いリンクプロセスとリンカースクリプトを理解しようとしています... binutils docを見ると、いくつかのコマンドを追加することで改善された単純なリンカースクリプトの実装が見つかりました。

OUTPUT_FORMAT("elf32-i386", "elf32-i386",
          "elf32-i386")
OUTPUT_Arch(i386)

ENTRY(mymain)

SECTIONS
{
   . = 0x10000;
   .text : { *(.text) }
   . = 0x8000000;
   .data : { *(.data) }
   .bss : { *(.bss) }
}

私のプログラムは非常に単純なプログラムです。

void mymain(void)
{
  int a;
  a++;
}

今、私は実行可能ファイルを構築しようとしました:

gcc -c main.c
ld -o prog -T my_script.lds main.o

しかし、progを実行しようとすると、起動時にSIGKILLを受け取ります。プログラムがコンパイルされ、コマンドにリンクされると、次のことがわかります。

gcc prog.c -o prog

最終的な実行可能ファイルは、crt1.ocrti.ocrtn.oなどの他のオブジェクトファイルの製品でもありますが、私の場合はどうでしょうか。このリンカースクリプトを使用する正しい方法はどれですか?

19
MirkoBanchi

コードは正常に実行されており、最後に問題が発生していると思われます。何が起こると思いますかafter _a++_?

mymain()は通常のC関数であり、呼び出し元に戻ろうとします。

ただし、これをELFエントリポイントとして設定しました。これは、プログラムセグメントが適切な場所にロードされると、ELFローダーにジャンプするように指示します。また、戻ることは期待されていません。

これらの「_crt1.o_、_crti.o_、_crtn.o_などの他のオブジェクトファイル」は通常、Cプログラムでこのようなものを処理します。 CプログラムのELFエントリポイントはmain()ではありません-代わりに、main()の適切な環境を設定するラッパーです(たとえば、argcとスタックまたはレジスタ内のargv引数(プラットフォームに応じて)、main()を呼び出し(戻る可能性があることを期待して)、次にexitシステムコールを呼び出します( main()からの戻りコードを使用)。


[次のコメントを更新:]

gdbで例を試してみると、mymain()から戻ると、実際に失敗することがわかります。mymainにブレークポイントを設定し、命令をステップ実行した後、インクリメントを実行してから、関数エピローグで問題が発生することを確認してください。

_$ gcc -g -c main.c
$ ld -o prog -T my_script.lds main.o
$ gdb ./prog
...
(gdb) b mymain
Breakpoint 1 at 0x10006: file main.c, line 4.
(gdb) r
Starting program: /tmp/prog 

Breakpoint 1, mymain () at main.c:4
4         a++;
(gdb) display/i $pc
1: x/i $pc
0x10006 <mymain+6>:     addl   $0x1,-0x4(%ebp)
(gdb) si
5       }
1: x/i $pc
0x1000a <mymain+10>:    leave  
(gdb) si
Cannot access memory at address 0x4
(gdb) si
0x00000001 in ?? ()
1: x/i $pc
Disabling display 1 to avoid infinite recursion.
0x1:    Cannot access memory at address 0x1
(gdb) q
_

少なくともi386の場合、ELFローダーはロードされたコードを入力する前に適切なスタックをセットアップするので、can ELFエントリポイントをC関数に設定し、適切な動作を取得します。ただし、前述したように、クリーンなプロセス出口を自分で処理する必要があります。また、Cランタイムを使用していない場合は、Cランタイムに依存するライブラリも使用しない方がよいでしょう。

これがその例です。元のリンカースクリプトを使用していますが、Cコードを変更してaを既知の値に初期化し、exitシステムコールを(インラインアセンブリを使用して)呼び出します。終了コードとしてのaの最終値。 (注:使用しているプラ​​ットフォームを正確に言っていないことに気づきました。ここではLinuxを想定しています。)

_$ cat main2.c
void mymain(void)
{
  int a = 42;
  a++;
  asm volatile("mov $1,%%eax; mov %0,%%ebx; int $0x80" : : "r"(a) : "%eax" );
}
$ gcc -c main2.c
$ ld -o prog2 -T my_script.lds main2.o
$ ./prog2 ; echo $?
43
$ 
_
22

はい、Linuxで実行するには、.ldsファイルを変更する必要があります

SECTIONS
{
   . = 0x8048000;
   .text : { *(.text) 
}
2
sharath kumar