81158


自作OSで文字描画を行うため、フォントのデータをobjcopyで埋め込んだのですが、その値が上手く取り出せないため、以下操作の中でおかしな点があれば、お教えいただきたいです。  
また、足りない情報などあれば、それもお教えいただけると幸いです。  
まず、フォントのビットマップ(8x16)をダウンロードし、バイナリに変換して、objcopyでELFファイルに埋め込みました。

[code]

    $ objcopy -I binary -O elf64-x86-64 -B i386:x86-64 font.bin font.o
    $ ld -z norelro --entry main -static -T./ld.scr main.o font.o ....(省略）
    
[/code]

生成したELFファイルをreadelfで読むと、以下のようなシンボルが生成されていることが確認できました。

[code]

    $ readelf kernel.elf -aW
    ...(省略）
    24: 0000000000001000   0 NOTYPE GLOBAL DEFAULT _binary_font_bin_size
    ...(省略)
    33: 00000000001021b8   0 NOTYPE GLOBAL DEFAULT _binary_font_bin_start
    34: 00000000001031b8   0 NOTYPE GLOBAL DEFAULT _edata
    35: 0000000000103200   0 NOTYPE GLOBAL DEFAULT _end
    36: 00000000001031b8   0 NOTYPE GLOBAL DEFAULT _binary_font_bin_end
    ...(省略)
    
[/code]

また、objdumpを使って実行ファイルを、バイナリエディタを使ってビットマップ用のバイナリを読みました。なお、ビットマップ用のバイナリは8bit(横幅)*16(縦幅)*256(文字数)がずらっと並ぶだけの形式です。

[code]

    $ objdump kernel.elf -s
    ...(省略)
    1025c8 00101028 28284444 447c8282 82820000
    ...(省略)
    
[/code]

[code]

    (バイナリエディタの出力)
    ...(省略)
    0000:0410 00101028 28284444 447c8282 82820000
    ...(省略)
    
[/code]

かなり長大なので大幅に省略しましたが、内容はしっかり同じで、上の0x41('A')が_binary_font_bin_startから0x410だけ先にあるように、おそらくデータは正しく埋め込めているだろうということは確認できました。  
そこで、ビットマップを取得し、画面に描画を行おうとしたところ、何故か上手く行かなかったため、原因を調査しました。フォントの取得と描画は以下のような関数を用意して行いました。

[code]

    // print.h 以下、ヘッダのincludeやプロトタイプ宣言、エラー処理などは省略
    extern const uint8_t _binary_font_bin_start;
    extern const uint8_t _binary_font_bin_end;
    extern const uint8_t _binary_font_bin_size;
    
[/code]

[code]

    // print.c
    // フォントの横幅は8なので、8bit整数のポインタで返す
    const uint8_t *get_font(char c) {
      uint32_t offset = (uint32_t)c * 16;   // 16はフォントの高さ,offsetは文字の最上段行を指すはず
      return &_binary_font_bin_start + offset;
    }
    
    int put_char(char c) {
      const uint8_t *font = get_font(c);
      uint16_t row, column;                // 本来は描画する左上の座標を代入するが、ここでは省略
      for(int16_t i = 0; i < 16; i++) {
        for(int16_t j = 7; j >= 0; j--) {
          if(font[i] & (1<<j))
            draw_dot(column+j, row+i, make_color(0xff, 0xff, 0xff));
          // draw_dotは点の描画、make_colorは(r,g,b)形式で色を作成
        }
      }
    
[/code]

ここで、まずはget_font()がおかしなアドレスを返しているのではないかと思い、その値を調べました。

[code]

    // main.c
    int main(frame_buffer_info_t frame_buffer) {
      ......(省略)
      // 画面に文字が打てないので、UEFIに値を返して表示させる。
      return (uint32_t)get_font('\0');
    }
    
[/code]

[code]

    // 出力
    Return Value: 0x1021B8
    
[/code]

これはreadelfで出力した_binary_font_bin_startのアドレスと一致するので、おかしな値ではないと判断しました。また、'A'で試した際は、objdumpで'A'のビットマップを指したアドレス値(0x1025c8)を出力しました。次は、'A'のビットマップを引き出し、1つ目の値を出力させようとしました。

[code]

    // main.c
    int main(frame_buffer_info_t frame_buffer) {
     ...(省略)
      const uint8_t *res = get_font('A');
      return res[0];
    }
    
[/code]

[code]

    // 出力
    Return Value: 0x65
    
[/code]

これは明らかにおかしな値(ビットマップの値と全く一致していない)ですが、これがどこから引き出された値なのかがよくわかりません。上記操作でおかしな点をお教えいただきたいです。  
現状、どんな情報があれば解決できるのかもよくわかっていないため、足りない情報などありましたら、それもお教えいただけると幸いです。

追記：リンカスクリプトの内容は、ld
--verboseで引き出したデフォルトのスクリプトの中の、開始アドレス、ENTRY、SEARCH_DIRのみを変更したものになります。

[code]

    /* Script for -z combreloc -z separate-code */
    /* Copyright (C) 2014-2021 Free Software Foundation, Inc.
    ......(省略)
    /* - ENTRY(_start) */
    ENTRY(main)
    /* - SEARCH_DIR("=/usr........... */
    SEARCH_DIR("=../tools/build-newlib")
    SECTIONS
    {
      /* - PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS; */
      PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x100000)); . = SEGMENT_START("text-segment", 0x100000) + SIZEOF_HEADERS;
      .interp      : { *(.interp) }
      ..........(以下、すべてデフォルトのもとの同じ)
    
[/code]

また、関係あるかはわかりませんが、ブートローダ側では、EFI_BOOT_SERVICES.ExitBootServices()をまだ呼び出していません。

追記その2：今度は、データ領域の初期化が上手く行っていない可能性を考え、試しにビットマップと同じ形式の初期化済みデータを一つだけ、グローバル領域と関数内に置いて、文字表示させることで比較しました。  
結果、関数内に置いたデータでは正しく文字表示できた一方、グローバル領域に置いたデータでは何も表示できませんでした。そのため、おそらくデータ領域の初期化が上手く行っていないことが原因であると推測しています。


CC BY-SA 4.0
2021-08-30T17:18:44.057
81127
2021-09-01T17:14:48.730
2021-08-31T15:54:58.547
44216
44216
question
c ポインタ
objcopyで埋め込んだデータが取り出せない


回答ではないのですが、動作確認を行ってみました。参考にしてみて下さい。

こちらの環境は以下の通りになります。

[code]

    $ lsb_release -ir
    Distributor ID: Ubuntu
    Release:        21.04
    
    $ gcc --version
    gcc (Ubuntu 11.1.0-1ubuntu1~21.04) 11.1.0
    
    $ ld --version
    GNU ld (GNU Binutils for Ubuntu) 2.36.1
    
[/code]

フォントデータは `/dev/urandom` から適当に作成しました。

[code]

    $ cat /dev/urandom | head -c $((8*16*256)) > font.bin
    $ stat --format='%s' font.bin
    32768
    $ od -w8 -N16 -Ax -tx1 font.bin
    000000 05 f5 ac ee b9 9e a5 a3
    000008 ec f8 e7 00 ac 2a d1 97
    
    $ objcopy -I binary -O elf64-x86-64 -B i386:x86-64 font.bin font.o
    
[/code]

以下は検証用プログラムのソースコードです。

[code]

    #include <stdint.h>
    
    extern const uint8_t _binary_font_bin_start[];
    extern const uint8_t _binary_font_bin_end[];
    extern const uint8_t _binary_font_bin_size[];
    
    const uint8_t *get_font(char c) {
      uint64_t offset = (uint64_t)c * 16;
      return _binary_font_bin_start + offset;
    }
    
    int main() {
      const uint64_t end = (uint64_t)_binary_font_bin_end;
      const uint64_t size = (uint64_t)_binary_font_bin_size;
    
      const uint8_t *start = get_font('\0');
      const uint8_t *A = get_font('A');
    }
    
[/code]

上記の `main.c` をコンパイルして、質問文にあるのと同じ方法でリンクします。

[code]

    $ ld -z norelro --entry main -o kernel.elf -static -T./ld.scr main.o font.o
    
[/code]

次は GDB で処理を確認して行きます。

[code]

    $ gdb kernel.elf
          :
    
    (gdb) break main.c:15
    Breakpoint 1 at 0x101052: file main.c, line 16.
    (gdb) run
    Breakpoint 1, main () at main.c:16
    16        const uint8_t *start = get_font('\0');
    (gdb) p/x (uint64_t)_binary_font_bin_start
    $1 = 0x103000
    (gdb) p/x (uint64_t)_binary_font_bin_end
    $2 = 0x10b000
    (gdb) p (uint64_t)_binary_font_bin_end - (uint64_t)_binary_font_bin_start
    $3 = 32768
    (gdb) p (uint64_t)_binary_font_bin_size
    $4 = 32768
    
[/code]

ここで、埋め込んだ `font.bin` データの開始位置と終了位置、サイズが合致している事が判るかと思います。

[code]

    (gdb) p/tz _binary_font_bin_start[0] @ 16
    $5 = {
      0x05, 0xf5, 0xac, 0xee, 0xb9, 0x9e, 0xa5, 0xa3,
      0xec, 0xf8, 0xe7, 0x00, 0xac, 0x2a, 0xd1, 0x97
    }
    
[/code]

`_binary_font_bin_start` から 16 バイトを表示してみると、先程の `od` コマンドによる結果と一致しています。

[code]

    $ od -w8 -N16 -Ax -tx1 font.bin
    000000 05 f5 ac ee b9 9e a5 a3
    000008 ec f8 e7 00 ac 2a d1 97
    
[/code]

また、`get_font()` の結果から参照してみると、こちらも一致しています。

[code]

    # get_font('\0')
    (gdb) p/tz start[0] @ 16
    $11 = {
      0x05, 0xf5, 0xac, 0xee, 0xb9, 0x9e, 0xa5, 0xa3,
      0xec, 0xf8, 0xe7, 0x00, 0xac, 0x2a, 0xd1, 0x97
    }
    
    # get_font('A')
    (gdb) p/tz A[0] @ 16
    $12 = {
      0x10, 0xb2, 0xaa, 0xdb, 0x9d, 0x58, 0x49, 0x9d,
      0xd5, 0x5d, 0x6d, 0xca, 0xd2, 0x92, 0x4d, 0x71
    }
    
    (gdb) p/tz _binary_font_bin_start['A'*8] @ 16
    $13 = {
      0x10, 0xb2, 0xaa, 0xdb, 0x9d, 0x58, 0x49, 0x9d,
      0xd5, 0x5d, 0x6d, 0xca, 0xd2, 0x92, 0x4d, 0x71
    }
    
    # font.bin
    $ od -w8 -N16 -j$(($(printf '%d' \'A)*16)) -Ax -tx1 font.bin
    000410 10 b2 aa db 9d 58 49 9d
    000418 d5 5d 6d ca d2 92 4d 71
    
[/code]

~~**閑話休題**

> そのため、おそらくデータ領域の初期化が上手く行っていないことが原因であると推測しています。

前述の検証用のプログラムでは `_binary_font_bin_start` 等を配列として `extern` しています。また、`main()`
の内部で `_binary_font_bin_end` と `_binary_font_bin_size` のアドレス値を取り出しています(ポインタ型を
`uint64_t` 型に cast)。

[code]

    const uint64_t end = (uint64_t)_binary_font_bin_end;
    const uint64_t size = (uint64_t)_binary_font_bin_size;
    
[/code]

`main.c` をコンパイルして `main.o` に含まれるシンボルを確認してみます。

[code]

    $ nm main.o
                     U _GLOBAL_OFFSET_TABLE_
                     U _binary_font_bin_end
                     U _binary_font_bin_size
                     U _binary_font_bin_start
    0000000000000000 T get_font
    000000000000002d T main
    
[/code]

一方、`end` や `size` の定義を削除して `extern` 宣言のみにする場合は以下です。

[code]

    $ nm main.o
                     U _GLOBAL_OFFSET_TABLE_
                     U _binary_font_bin_start
    0000000000000000 T get_font
    000000000000002d T main
    
[/code]

`get_font()` で参照されている `_binary_font_bin_start` だけになってしまいました。

`extern` 宣言のみのシンボルも含める方法があるかと思うのですが、現時点ではまた分かっていません。  
~~


CC BY-SA 4.0
2021-09-01T07:18:34.293
81158
2021-09-01T07:50:46.607
2021-09-01T07:50:46.607
47127
47127
81127
answer


質問者です。自分でも検証したところ、原因がわかったので記します。

結論としては、ローダー側の実装ミスで、カーネル側のプログラムには(少なくとも今直面している問題に対しては）問題ありませんでした。  
犯してしまった実装ミスは、data領域を正しくロードしなかったことです。私は何も考えずにカーネルの実行ファイルを目的のアドレスにロードしただけで、data領域始め、どの領域もヘッダ通りのアドレスにロードしていませんでした。  
以下のようにreadelfすると、ファイル内のdata領域が格納されている領域のファイルオフセットと、ファイルのロード先アドレスとdata領域の先頭アドレスの差分は一致していません。（text領域はファイルオフセットと、先頭アドレスとロード先アドレスの差分が一致しているので、奇跡的にプログラム本体は動作しているということだと考えています。）

[code]

    $ readelf kernel.elf -SW
    ....(省略して、必要部分だけ明記)
    セクションヘッダ：
     [番] 名前  型       アドレス          Off 
     [1] .text PROGBITS 0000000000101000 001000........
     [3] .data PROGBITS 00000000001031b8 0021b8........
    
[/code]

そこで、試しにget_font()を以下のように修正し、ファイルオフセットとロード先アドレスの差分を吸収してみました。

[code]

    extern const uint8_t _binary_font_bin_start[];
    
[/code]

[code]

    const uint8_t *get_font(char c) {
      uint32_t offset = (uint32_t)c * 16;
      return _binary_font_bin_start + offset - 0x1000;
    }
    
[/code]

すると、正常にデータの読み出しに成功し、正しく描画することに成功しました。

ですので、本質問は結果的には見当違いの質問となっておりました。多数コメントいただき、検証までしていただいたmetropolisさんには大変申し訳ございません。  
本質問はmetropolisさんのご回答にチェックをつけてクローズさせていただきます。


CC BY-SA 4.0
2021-09-01T17:14:48.730
81169
2021-09-01T17:14:48.730
44216
81127
answer