Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 140] ファイルの概要

このコミットは、Go言語の初期のコンパイラおよびリンカにおけるELF(Executable and Linkable Format)ヘッダの生成ロジックを大幅に更新するものです。特に、Linuxシステム上で実行可能なバイナリの構造を定義するELFプログラムヘッダとセクションヘッダの記述方法が変更されています。これにより、生成されるGoバイナリの互換性、構造、およびデバッグ情報が改善されたと考えられます。

コミット

commit 6a659ebf1360b36107a2fca01d3434df10de7a09
Author: Ken Thompson <ken@golang.org>
Date:   Mon Jun 9 13:16:50 2008 -0700

    new elf header
    
    SVN=121737

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/6a659ebf1360b36107a2fca01d3434df10de7a09

元コミット内容

new elf header

SVN=121737

変更の背景

このコミットが行われた2008年6月は、Go言語がまだGoogle社内で開発初期段階にあった時期です。Go言語のコンパイラとリンカは、その当時から自己ホスト型(Go自身でGoのコードをコンパイルできる)を目指して開発が進められていました。生成されるバイナリは、ターゲットとなるOS(この場合はLinux)の標準的な実行可能形式であるELFに準拠する必要があります。

初期のGoコンパイラ/リンカが生成するELFバイナリは、おそらく機能が限定的であったり、標準的なELFツールとの互換性に課題があったりしたと考えられます。特に、デバッグ情報やシンボル情報、セクションの配置などが最適化されていなかった可能性があります。

この「new elf header」というコミットメッセージは、ELFバイナリの構造、特にプログラムヘッダ(PT_LOADセグメントなど)とセクションヘッダ(.text, .data, .bss, .shstrtab, .gosymtabなど)の生成ロジックを刷新し、より堅牢で標準に準拠したバイナリを生成することを目的としています。これにより、GoプログラムがLinux環境でより安定して動作し、デバッグや解析が容易になる基盤が築かれたと推測されます。

前提知識の解説

ELF (Executable and Linkable Format)

ELFは、Unix系オペレーティングシステム(Linux、FreeBSDなど)で広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。ELFファイルは、大きく分けて以下の2つのビューを持ちます。

  1. 実行ビュー (Execution View): プログラムの実行時にメモリにどのようにロードされるかを記述します。これは主にプログラムヘッダ (Program Header) によって定義されます。プログラムヘッダは、実行可能ファイル内のセグメント(メモリ領域)の配置、サイズ、パーミッション(読み取り、書き込み、実行)などを指定します。

    • PT_LOAD: 最も一般的なプログラムヘッダタイプで、ファイルの一部をメモリにロードするセグメントを定義します。通常、コード(.text)と読み取り専用データ、および書き込み可能データ(.data, .bss)がそれぞれPT_LOADセグメントとして扱われます。
    • PF_X (Executable): 実行可能パーミッション。
    • PF_W (Writable): 書き込み可能パーミッション。
    • PF_R (Readable): 読み取り可能パーミッション。
  2. リンクビュー (Linking View): リンク時にオブジェクトファイルがどのように結合されるかを記述します。これは主にセクションヘッダ (Section Header) によって定義されます。セクションヘッダは、ファイル内の論理的なブロック(コード、データ、シンボルテーブルなど)の配置、サイズ、属性などを指定します。

    • .text: 実行可能なコードが含まれるセクション。
    • .data: 初期化されたデータが含まれるセクション。
    • .bss: 初期化されていないデータ(ゼロ初期化される)が含まれるセクション。
    • .shstrtab: セクション名の文字列テーブルが含まれるセクション。
    • .symtab: シンボルテーブルが含まれるセクション。
    • .gosymtab: Go言語特有のシンボル情報が含まれるセクション(このコミットで導入された可能性が高い)。

Go言語のコンパイルとリンカ (6g, 6l)

Go言語の初期のツールチェインでは、各アーキテクチャ(例: amd64)に対応するコンパイラとリンカが特定の命名規則を持っていました。

  • 6g: gc (Go Compiler) のamd64アーキテクチャ版。Goソースコードをオブジェクトファイルにコンパイルします。
  • 6l: gl (Go Linker) のamd64アーキテクチャ版。オブジェクトファイルをリンクして実行可能バイナリを生成します。

このコミットは主に6l(リンカ)の挙動に影響を与えています。リンカは、コンパイラが生成したオブジェクトファイルやライブラリを結合し、最終的な実行可能ファイルを生成する役割を担います。この過程で、ELFヘッダやセグメント、セクションの配置を決定します。

INITTEXT, INITDAT, INITRND

これらはGoリンカ内部で使用される定数で、それぞれ以下の意味を持ちます。

  • INITTEXT: テキストセグメント(コード)がメモリにロードされる開始アドレス。
  • INITDAT: データセグメントがメモリにロードされる開始アドレス。
  • INITRND: メモリのアライメント(配置の境界)に関する定数。通常、ページサイズ(4KBなど)の倍数にアライメントされます。

技術的詳細

このコミットの主要な変更点は、src/cmd/6l/asm.cにおけるELFヘッダの生成ロジックの再構築です。

  1. プログラムヘッダの再定義:

    • 以前はハードコードされていたプログラムヘッダの生成ロジックが、linuxphdrという新しいヘルパー関数に抽象化されました。
    • テキストセグメント (PT_LOAD, PF_X|PF_R) とデータセグメント (PT_LOAD, PF_X|PF_W|PF_R) の2つの標準的なセグメントに加えて、0x6474e551というカスタムタイプを持つ新しいセグメントが追加されました。このタイプはGo言語特有のセグメント(gokとコメントされている)である可能性が高く、Goランタイムやデバッグ情報に関連するメタデータを格納するために使用されるかもしれません。
  2. セクションヘッダの導入と詳細化:

    • 以前のELF生成ロジックでは、セクションヘッダに関する記述が非常に限定的でした。このコミットでは、linuxshdrという新しいヘルパー関数とlinuxstrtable関数が導入され、以下の標準的なセクションが明示的に定義されるようになりました。
      • .text: コードセクション。
      • .data: 初期化済みデータセクション。
      • .bss: 初期化されていないデータセクション。
      • .shstrtab: セクション名文字列テーブル。
      • .gosymtab: Go言語特有のシンボルテーブル。これはGoのデバッグやプロファイリングにおいて重要な情報を提供します。
    • セクションヘッダの数 (# of Shdrs) が0から5に増加し、セクションヘッダ文字列テーブルのインデックス (Shdr with strings) も設定されています。
    • 各セクションには、タイプ、フラグ(読み取り、書き込み、実行可能、割り当て可能など)、仮想アドレス、ファイルオフセット、サイズ、アライメントなどの詳細な属性が設定されています。
  3. HEADRINITTEXTの計算変更:

    • src/cmd/6l/obj.cにおいて、ELF64実行可能ファイルの場合のHEADR(ヘッダの合計サイズ)の計算が、固定値からlinuxheadr()関数による動的な計算に変更されました。これにより、ELFヘッダの構造変更に柔軟に対応できるようになりました。
    • INITTEXTのデフォルト値も0x400000L+HEADRから4096+HEADRに変更されています。これは、Linuxの標準的な実行可能ファイルのベースアドレス(通常0x400000)とページサイズ(4096バイト)を考慮した調整と考えられます。
  4. 浮動小数点数除算/剰余演算の修正 (src/cmd/6g/cgen.c):

    • OMOD (剰余) と ODIV (除算) 演算において、オペランドが浮動小数点型である場合に、適切なアセンブリ命令 (optoas) を選択するロジックが追加されました。これは、整数と浮動小数点数で除算/剰余の処理が異なるため、コンパイラが正しいコードを生成するための修正です。
  5. レキサーの修正 (src/cmd/gc/lex.c):

    • 文字列リテラル処理におけるエスケープ文字のチェック順序が変更されました。v < Runeself || escflagとなり、Unicodeの範囲外の文字かエスケープフラグが立っているかを先にチェックすることで、より堅牢な処理になったと考えられます。

これらの変更は、GoバイナリがLinuxシステムでより標準的なELF形式に準拠し、デバッグやシンボル解決の機能が強化されることを示しています。特に.gosymtabの導入は、Go言語のランタイムやツールが独自のシンボル情報を効率的に管理するための重要なステップです。

コアとなるコードの変更箇所

src/cmd/6l/asm.c

ELFヘッダの生成ロジックが大幅に変更されています。

--- a/src/cmd/6l/asm.c
+++ b/src/cmd/6l/asm.c
@@ -383,47 +388,122 @@ debug['s'] = 1;
 	cput(1);			/* version = CURRENT */
 	strnput("", 9);

-/*10*/		wputl(2);			/* type = EXEC */
+		wputl(2);			/* type = EXEC */
 	wputl(62);			/* machine = AMD64 */
 	lputl(1L);			/* version = CURRENT */
-/*18*/		llputl(entryvalue());		/* entry vaddr */
+		llputl(entryvalue());		/* entry vaddr */
 	llputl(64L);			/* offset to first phdr */
-		llputl(0L);			/* offset to first shdr */
-/*30*/		lputl(0L);			/* processor specific flags */
+		llputl(64L+56*3);		/* offset to first shdr */
+		lputl(0L);			/* processor specific flags */
 	wputl(64);			/* Ehdr size */
 	wputl(56);			/* Phdr size */
-		wputl(2);			/* # of Phdrs */
+		wputl(3);			/* # of Phdrs */
 	wputl(64);			/* Shdr size */
-		wputl(0);			/* # of Shdrs */
-		wputl(0);			/* Shdr string size */
-
-		lputl(1L);			/* text - type = PT_LOAD */
-		lputl(1L+4L);			/* text - flags = PF_X+PF_R */
-		llputl(HEADR);			/* file offset */
-		llputl(INITTEXT);		/* vaddr */
-		llputl(INITTEXT);		/* paddr */
-		llputl(textsize);		/* file size */
-		llputl(textsize);		/* memory size */
-		llputl(INITRND);		/* alignment */
+		wputl(5);			/* # of Shdrs */
+		wputl(4);			/* Shdr with strings */
+
+		linuxphdr(1,			/* text - type = PT_LOAD */
+			1L+4L,			/* text - flags = PF_X+PF_R */
+			HEADR,			/* file offset */
+			INITTEXT,		/* vaddr */
+			INITTEXT,		/* paddr */
+			textsize,		/* file size */
+			textsize,		/* memory size */
+			INITRND);		/* alignment */

-		lputl(1L);			/* data - type = PT_LOAD */
-		lputl(1L+2L+4L);		/* text - flags = PF_X+PF_W+PF_R */
 	v = rnd(HEADR+textsize, INITRND);
-		llputl(v);			/* file offset */
-		llputl(INITDAT);		/* vaddr */
-		llputl(INITDAT);		/* paddr */
-		llputl(datsize);		/* file size */
-		llputl(datsize+bsssize);	/* memory size */
-		llputl(INITRND);		/* alignment */
-
-//		lputl(0L);			/* data - type = PT_NULL */
-//		lputl(4L);			/* ro - flags = PF_R */
-//		llputl(HEADR+textsize+datsize);	/* file offset */
-//		llputl(0L);
-//		llputl(0L);
-//		llputl(symsize);		/* symbol table size */
-//		llputl(lcsize);			/* line number size */
-//		llputl(0x04L);			/* alignment */
+		linuxphdr(1,			/* data - type = PT_LOAD */
+			1L+2L+4L,		/* data - flags = PF_X+PF_W+PF_R */
+			v,			/* file offset */
+			INITDAT,		/* vaddr */
+			INITDAT,		/* paddr */
+			datsize,		/* file size */
+			datsize+bsssize,	/* memory size */
+			INITRND);		/* alignment */
+
+		linuxphdr(0x6474e551,		/* gok - type = gok */
+			1L+2L+4L,		/* gok - flags = PF_X+PF_R */
+			0,			/* file offset */
+			0,			/* vaddr */
+			0,			/* paddr */
+			0,			/* file size */
+			0,			/* memory size */
+			8);			/* alignment */
+
+		linuxshdr(nil,			/* name */
+			0,			/* type */
+			0,			/* flags */
+			0,			/* addr */
+			0,			/* off */
+			0,			/* size */
+			0,			/* link */
+			0,			/* info */
+			0,			/* align */
+			0);			/* entsize */
+
+		stroffset = 1;
+		v = HEADR;
+		linuxshdr(".text",		/* name */
+			1,			/* type */
+			6,			/* flags */
+			INITTEXT,		/* addr */
+			v,			/* off */
+			textsize,		/* size */
+			0,			/* link */
+			0,			/* info */
+			4,			/* align */
+			0);			/* entsize */
+
+		v += textsize;
+		linuxshdr(".data",		/* name */
+			1,			/* type */
+			3,			/* flags */
+			INITDAT,		/* addr */
+			v,			/* off */
+			datsize,		/* size */
+			0,			/* link */
+			0,			/* info */
+			4,			/* align */
+			0);			/* entsize */
+
+		v += datsize;
+		linuxshdr(".bss",		/* name */
+			8,			/* type */
+			3,			/* flags */
+			INITDAT,		/* addr */
+			v,			/* off */
+			bsssize,		/* size */
+			0,			/* link */
+			0,			/* info */
+			4,			/* align */
+			0);			/* entsize */
+
+		v += 0;
+		va = stroffset +
+			strlen(".shstrtab")+1 +
+			strlen(".gosymtab")+1;
+		linuxshdr(".shstrtab",		/* name */
+			3,			/* type */
+			0,			/* flags */
+			0,			/* addr */
+			v,			/* off */
+			va,			/* size */
+			0,			/* link */
+			0,			/* info */
+			4,			/* align */
+			0);			/* entsize */
+
+		v += va;
+		linuxshdr(".gosymtab",		/* name */
+			2,			/* type */
+			0,			/* flags */
+			0,			/* addr */
+			v,			/* off */
+			0,			/* size */
+			0,			/* link */
+			0,			/* info */
+			4,			/* align */
+			0);			/* entsize */
 	break;
 	}
 	cflush();
@@ -678,3 +758,79 @@ machheadr(void)

 	return a*4;
 }
+
+ulong
+linuxheadr(void)
+{
+	ulong a;
+
+	a = 64;		/* a.out header */
+
+	a += 56;	/* page zero seg */
+	a += 56;	/* text seg */
+	a += 56;	/* stack seg */
+
+	a += 64;	/* nil sect */
+	a += 64;	/* .text sect */
+	a += 64;	/* .data seg */
+	a += 64;	/* .bss sect */
+	a += 64;	/* .shstrtab sect - strings for headers */
+	a += 64;	/* .gosymtab sect */
+
+	return a;
+}
+
+
+void
+linuxphdr(int type, int flags, vlong foff,
+	vlong vaddr, vlong paddr,
+	vlong filesize, vlong memsize, vlong align)
+{
+
+	lputl(type);			/* text - type = PT_LOAD */
+	lputl(flags);			/* text - flags = PF_X+PF_R */
+	llputl(foff);			/* file offset */
+	llputl(vaddr);			/* vaddr */
+	llputl(paddr);			/* paddr */
+	llputl(filesize);		/* file size */
+	llputl(memsize);		/* memory size */
+	llputl(align);			/* alignment */
+}
+
+void
+linuxshdr(char *name, ulong type, vlong flags, vlong addr, vlong off,
+	vlong size, ulong link, ulong info, vlong align, vlong entsize)
+{
+	lputl(stroffset);
+	lputl(type);
+	llputl(flags);
+	llputl(addr);
+	llputl(off);
+	llputl(size);
+	lputl(link);
+	lputl(info);
+	lputl(align);
+	llputl(entsize);
+
+	if(name != nil)
+		stroffset += strlen(name)+1;
+}
+
+void
+linuxstrtable(void)
+{
+	char *name;
+
+	name = "";
+	strnput(name, strlen(name)+1);
+	name = ".text";
+	strnput(name, strlen(name)+1);
+	name = ".data";
+	strnput(name, strlen(name)+1);
+	name = ".bss";
+	strnput(name, strlen(name)+1);
+	name = ".shstrtab";
+	strnput(name, strlen(name)+1);
+	name = ".gosymtab";
+	strnput(name, strlen(name)+1);
+}

src/cmd/6l/l.h

新しい関数プロトタイプとグローバル変数stroffsetが追加されています。

--- a/src/cmd/6l/l.h
+++ b/src/cmd/6l/l.h
@@ -344,6 +344,7 @@ EXTERN	int	imports, nimports;
 EXTERN	int	exports, nexports;
 EXTERN	char*	EXPTAB;
 EXTERN	Prog	undefp;
+EXTERN	ulong	stroffset;

 #define	UP	(&undefp)

@@ -424,6 +425,15 @@ void	machsect(char*, char*, vlong, vlong, ulong, ulong, ulong, ulong, ulong);
 void	machstack(vlong);
 ulong	machheadr(void);

+ulong	linuxheadr(void);
+void	linuxphdr(int type, int flags, vlong foff,
+	vlong vaddr, vlong paddr,
+	vlong filesize, vlong memsize, vlong align);
+void	linuxshdr(char *name, ulong type, vlong flags, vlong addr, vlong off,
+	vlong size, ulong link, ulong info, vlong align, vlong entsize);
+void	linuxstrtable(void);
+
+
 #pragma	varargck	type	"D"	Adr*
 #pragma	varargck	type	"P"	Prog*
 #pragma	varargck	type	"R"	int

src/cmd/6l/obj.c

HEADRINITTEXTの計算ロジックが変更されています。

--- a/src/cmd/6l/obj.c
+++ b/src/cmd/6l/obj.c
@@ -195,9 +195,9 @@ main(int argc, char *argv[])
 			INITRND = 4096;
 		break;
 	case 7:	/* elf64 executable */
-		HEADR = rnd(64L+2*56L, 16);
+		HEADR = linuxheadr();
 		if(INITTEXT == -1)
-			INITTEXT = 0x400000L+HEADR;
+			INITTEXT = 4096+HEADR;
 		if(INITDAT == -1)
 			INITDAT = 0;
 		if(INITRND == -1)

src/cmd/6g/cgen.c

浮動小数点数除算/剰余演算の処理が追加されています。

--- a/src/cmd/6g/cgen.c
+++ b/src/cmd/6g/cgen.c
@@ -175,6 +175,10 @@ cgen(Node *n, Node *res)

 	case OMOD:
 	case ODIV:
+		if(isfloat[n->type->etype]) {
+			a = optoas(n->op, nl->type);
+			goto abop;
+		}
 		cgen_div(n->op, nl, nr, res);
 		break;

src/cmd/gc/lex.c

レキサーの文字列処理ロジックが変更されています。

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -227,7 +227,7 @@ l0:
 		for(;;) {
 			if(escchar('"', &escflag, &v))
 				break;
-			if(escflag || v < Runeself) {
+			if(v < Runeself || escflag) {
 				cp = remal(cp, c1, 1);
 				cp[c1++] = v;
 			} else {

コアとなるコードの解説

このコミットの核となる変更は、Goリンカ(6l)がLinux向けELFバイナリを生成する際の内部構造とロジックの刷新です。

  1. linuxphdr関数の導入:

    • この関数は、ELFプログラムヘッダ(Program Header Table, PHT)のエントリを書き込むためのヘルパーです。PHTは、実行時にOSがバイナリをメモリにロードする方法を記述します。
    • 引数には、セグメントのタイプ(PT_LOADなど)、フラグ(読み取り、書き込み、実行可能)、ファイルオフセット、仮想アドレス、物理アドレス、ファイルサイズ、メモリサイズ、アライメントなどが渡されます。
    • これにより、コード(.text)とデータ(.data, .bss)のセグメントが適切に定義され、メモリにロードされる際のパーミッションや配置が正確に制御されます。特に、PF_X(実行可能)、PF_W(書き込み可能)、PF_R(読み取り可能)の組み合わせで、各セグメントのメモリ保護が設定されます。
    • 0x6474e551というカスタムタイプを持つgokセグメントは、Goランタイムが内部的に使用する特殊なデータやメタデータを格納するために予約された領域である可能性が高いです。
  2. linuxshdr関数の導入:

    • この関数は、ELFセクションヘッダ(Section Header Table, SHT)のエントリを書き込むためのヘルパーです。SHTは、リンク時にファイル内の論理的なブロック(セクション)を識別し、その属性を記述します。
    • 引数には、セクション名、タイプ(SHT_PROGBITSSHT_NOBITSSHT_STRTABSHT_SYMTABなど)、フラグ、アドレス、オフセット、サイズ、リンク、情報、アライメント、エントリサイズなどが渡されます。
    • この関数により、.text.data.bssといった標準的なセクションに加えて、.shstrtab(セクション名文字列テーブル)と.gosymtab(Goシンボルテーブル)が明示的に定義されるようになりました。
    • .gosymtabの導入は特に重要で、Go言語のデバッグツールやプロファイリングツールが、Go特有の関数や変数、型情報などを効率的に取得するための基盤となります。これは、Goのツールチェインが成熟していく上で不可欠な要素です。
  3. linuxstrtable関数の導入:

    • この関数は、セクション名文字列テーブル(.shstrtab)の内容を生成します。ELFファイルでは、セクション名は直接文字列として格納されるのではなく、文字列テーブルへのオフセットとして参照されます。この関数は、定義された各セクションの名前(例: ".text", ".data", ".gosymtab")をこのテーブルに書き込みます。
  4. linuxheadr関数の導入:

    • この関数は、ELFヘッダ全体のサイズを計算します。以前は固定値で計算されていたものが、プログラムヘッダやセクションヘッダの数とサイズに基づいて動的に計算されるようになりました。これにより、ELF構造の変更に対してリンカがより柔軟に対応できるようになります。

これらの変更により、Goリンカはより標準的で詳細なELFバイナリを生成できるようになり、Goプログラムの実行時の安定性向上、デバッグ機能の強化、および他のELFツールとの互換性向上が図られました。

関連リンク

参考にした情報源リンク