Yabu.log

色々勉強するブログです。

GDBのコマンド出力をファイルに保存する方法

GDBで実行しているPGのファイルの出力は、リダイレクトなどを 使って簡単にファイルに保存することが可能です。

例えば"helloworld"をただ出力するだけのプログラム の出力結果,"helloworld"という文字列をファイルに保存するのは簡単です。

しかし、GDBのコマンド(メモリダンプなど)を行った時の出力結果を保存するのに少し手間がかかったのでここで紹介したいと思います。

GDBの出力するファイルを指定する

以下のコマンドで出力先ファイルの設定、出力開始を指定できます。

set logging file <ファイル名>
set logging on

ファイルへの出力をやめるときは以下のコマンドです

set logging off

http://www.geocities.jp/harddiskdive/gdb/gdb_13.html

ページネーションを無効化する

たくさんメモリダンプした時など、一気に出さずに 続けて出すならEnter押してね~というメッセージが出ます

--Type <return> to continue, or q <return> to quit--- / “debugging --

これはデフォルトでONになっており、邪魔ですが以下のコマンドで無効化できます。

set pagination off

https://stackoverflow.com/questions/28815621/how-to-disable-type-return-to-continue-or-q-return-to-quit-in-gdb

これは.gdbinitファイルに加えたほうがいいかもしれません。

まとめて見ると意外と簡単ですが、これを探し出すのにかなり時間がかかりました。

GDBのコマンド

GDBの動かし方について。

オンラインで試せるGDBがあったので遊び倒して見た。

GDB online Debugger | Compiler - Code, Compile, Run, Debug online C, C++

よく使う(使った)コマンドなどをまとめた。

シンボルファイル関連は後日やりたい。

コマンド 省略形 意味
set disassembly-flavor intel - アセンブラの表示をintelのものにできる
list [<行数>|<関数名>] l 関数または行数から10行周辺のソースを表示
break [<行数>|<関数名>] b 関数、または行数の位置にブレークポイントを置く
clear [<行数>|<関数名>] 関数、または行数の位置のブレークポイントを削除する
dlete bnums ... 指定されたブレークポイント番号のブレークポイントを削除する
step s シンボルファイルで紐付いたプログラムを1行実行(関数の中に入る)
stepi si 単一の機械語を一命令実行
next n シンボルファイルで紐付いたプログラムを1行実行(関数は中に入らない)
nexti ni 単一の機械語を一命令実行、サブルーチンはリターンするまで実行する
backtrace bt 関数の呼び出しトレースを表示
print <変数名> p 変数の内容を表示。&をつけてアドレスを表示したり*を付けて値参照したりできる。

メモリダンプのコマンド

  • x/<量><単位><表示形式> <アドレス>
    • 量:数字
    • 単位
      • b=1Byte
      • h=2Byte
      • w=4Byte
      • g=8Byte
    • 表示形式
      • x=符号なし16進数
      • d=符号付き10進数
      • u=符号なし10進数
      • o=符号なし8進数
      • a=絶対アドレス?
      • c=文字定数
      • f=浮動小数点(単位がw,gの時のみ有効)
      • s=文字列(null文字までを1単位とする)
      • i=機械語

起動時に -ex <ファイルまたは''囲いのコマンド>オプションをつける事で起動時にgdbにコマンドを実行させられる。

自分の場合はqemuで1234ポートでgdbサーバーを起動した後、クライアント側のgdbgdb -ex 'target remote localhost:1234'とコマンドを打つ事ですぐさまデバッグが始められる。*1

他メモ

Macではbrewで入れたGDBをそのまま動かせなかった

qiita.com

見やすいGDBのUI。良さそう

qiita.com

.gdbinitの設定方法

gdbで初期設定をAT&T記法からIntel記法に変更する方法 | サラリーマンがハッカーを真剣に目指す

*1:もちろん起動時に行いたい長ったらしい処理をファイル外だしにして引数に与えることも可能

QEMUにGDBを繋げてhariboteOSをデバッグする方法

QEMUGDBを繋げてhariboteOSを調査したいと思います。

とりあえず初期設定っぽいことはできました。

主に観測したい事

ブレイクポイントを適当に設定して以下のものを観測したい

  • IPLの動作でフロッピーの内容がメモリにロードされる様子
  • リアルモード・プロテクトモード切り替え前後
  • 画面描画直前・直後
  • 割り込みハンドラにブレイクポインタを置く(マルチタスクなどをデバッグ)

QEMU × GDBについて

https://en.wikibooks.org/wiki/QEMU/Debugging_with_QEMU

  • -S -gdb stdio:使えなかった
  • -S -gdb tcp::9000:使えた
  • -s -S:使えた

-SはデバッガのコマンドまでCPUを起動させないオプション。-s-gdb tcp::1234.の意味

z_tools/qemu/Makefilegdbを使うように-s -Sを書き足す

編集前

QEMU     = qemu-system-i386
QEMU_ARGS   = -L . -m 32 -localtime -vga std -fda fdimage0.bin -monitor stdio

default:
    $(QEMU) $(QEMU_ARGS)

編集後

QEMU     = qemu-system-i386
QEMU_ARGS   = -L . -m 32 -localtime -vga std -fda fdimage0.bin -monitor stdio -s -S
default:
    $(QEMU) $(QEMU_ARGS)

この状態でmake run. すると画面が黒い状態で止まる(-SオプションでCPUを止めているため)

別ウインドウを開き、gdb起動後にコンソールに以下を打ち込む

target remote localhost:1234

デバッガが接続される。この状況でcontinueと打ち込むと...

www.youtube.com

CPUが動きだしhariboteOSが起動する(break pointをまだ設定していないため特にデバッグっぽいことはできない)

主に観測したい事に関しては次回以降。。。

30日でできる! OS自作入門

30日でできる! OS自作入門

C言語復習ラスト。ライブラリについて

ライブラリは今までよく知らなかったので、復習ではない気がする。

ライブラリとは

このプロセスの中の .oファイルをまとめてライブラリを作ることができる

$ ar -r mylib.a a.o b.o
ar: creating archive mylib.a

$ ls -alt
-rw-r--r--   1   staff  1768  7 17 00:07 mylib.a
-rw-r--r--   1   staff   748  7 16 19:13 b.o
-rw-r--r--@  1   staff   748  7 16 19:13 a.o

$ ar -t mylib.a 
__.SYMDEF SORTED
a.o
b.o
  • 関数を使うにはプロトタイプ宣言が必要
    • #include<stdio.h>とmain関数の間にある関数の宣言のやつ
      • main関数の手前に関数を定義してしまえばプロトタイプ宣言を書かなくて良いのではないだろうか?(手抜き)
        • この方法を採用すると、呼び出し関係を把握して、関数の依存性を整理して順番に定義する必要がある(非常に手間)
#include<stdio.h>
//このプロトタイプ宣言を入れとけば関数の依存関係は気にしなくて良くなる。
//void a();
//void b();

void a(){
  b();//関数bを呼び出しているが、この呼び出しより上に定義されていないためコンパイルエラー
}
void b(){
  puts("test");
}
int main(){
  a();
}
コンパイルエラー
other/prototype.c:4:3: warning: implicit declaration of function 'b' is invalid in C99
      [-Wimplicit-function-declaration]
  b();
  ^
other/prototype.c:6:6: error: conflicting types for 'b'
void b(){
     ^
other/prototype.c:4:3: note: previous implicit declaration is here
  b();
  ^

mylib.hの内容

void a();
void b();

libtest.cの内容

#include "mylib.h"
main()
{
  a();
  b();
}

もちろんライブラリを利用するプログラムは.hファイルを展開するプリプロセッサを使わなくても 自分でライブラリの中の関数をプロトタイプ宣言に追記すれば利用することができる。ただし、こういうことは面倒なので、基本的には.hファイルを用意して#includeすべき

void a();
void b();
main()
{
  a();
  b();
}

#includeプリプロセッサの利用有無による結果の違い

  • gcc -Eコマンドの結果も大して変わらない

#includeプリプロセッサ利用

$ gcc -E libtest.c mylib.a
clang: warning: mylib.a: 'linker' input unused [-Wunused-command-line-argument]
# 1 "libtest.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 341 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "libtest.c" 2
# 1 "./mylib.h" 1
void a();
void b();
# 2 "libtest.c" 2
main()
{
  a();
  b();
}
$ gcc -E libtest.c mylib.a
clang: warning: mylib.a: 'linker' input unused [-Wunused-command-line-argument]
# 1 "libtest.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 341 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "libtest.c" 2
void a();
void b();
main()
{
  a();
  b();
}

まとめ

ライブラリを作ったなら.hファイルも忘れず作ろう!

おしまい。

プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み

プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み

C言語復習その6。プリプロセッサ、リンカについて

本書ラストのライブラリまで行けると思ったが、時間がなくなった。 もっと有意義に3連休を過ごせなかったのだろうか。(反省)

プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み

プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み

ソースコードから実行ファイル(exe,elf)などができるまで

の処理で処理される。

  • コンパイラは自動的に1,3の処理を呼んでいる
  • 本書の想定だとそれぞれcpp.exe,gcc.exe,ld.exeを使っているようだ
  • cppってC++用のコンパイラだと思っていたのだが...
    • C Preprocessorの略らしい

プリプロセッサ

  • #ハッシュで始まる行を対象に処理をする仕組み

    • #defineで定義されている固定値を置き換える
  • gccでは-Eオプションで実行することにより、プリプロセッサの結果を標準入出力に表示できる

元ソース

#include<stdio.h>
//defineで値を定義
#define bignumber 1000

//マクロを定義
#define max(a,b) (a >b)? a: b

int main(){
  int a = bignumber;
  printf("%d is larger\n", max(100,10));
  printf("%d\n",a);
  printf("%d\n",bignumber);
}

gcc -Eの出力(※stdio.hの展開が巨大すぎるのでそれは略している)

・・・(stdio.hの展開略)

int main(){
  int a = 1000;
  printf("%d is larger\n", (100 >10)? 100: 10);
  printf("%d\n",a);
  printf("%d\n",1000);
}
  • マクロの注意点
    • 関数と違って「型」の情報がない
    • 演算子の優先順位でハマる可能性がある
    • 例えば
int a=5,b=4;
printf("%d\n",max(a, ++b));//6が表示される
int a=5,b=4;
printf("%d\n",(a >++b)? a: ++b);
  • 例2:以下は大きい方に+100された値を期待しているが。。。
    • printf("larger + 100=%d\n",max(20,10)+100);のソースが
    • printf("larger + 100=%d\n",(20 >10)? 20: 10 +100);と展開される
      • この3項演算子(20>10)?10:110となるため評価結果は20となる
    • 出力結果はlarger + 100=20となる。
  • undef#defineで定義した値を取り下げる
# define max 5
printf("%d\n",max);
# undef max
#define max 10
printf("%d\n",max);
- `gcc -E`で以下のように展開される
printf("%d\n",5);


printf("%d\n",10);
  • ifdefでdefineの定義に応じてコードを切り替えられる
    • 昔nmapのソースを読もうと思ったことがあったが、コードに#ifdef DEBUGみたいなのを散見した
    • 多分テストやデバッグ時のコードをプロダクションのバイナリに含まされなくない、というようなことをやっている
  • 組み込みマクロというものもある。
    • コンパイラ?ライブラリ?にあらかじめ用意されている
      • ソースprintf("%s:%d:%s\n",__FILE__,__LINE__,__func__);
      • プリプロセッサ適応後printf("%s:%d:%s\n","chap6/preprocesser.c",39,__func__);
      • 実行結果chap6/preprocesser.c:39:main
        • 正確には__func__はマクロではなく前定義識別名というらしい

リンカ

  • ソース -> 実行ファイルの変換の大トリのやつ。
  • コンパイラ.cファイルから.oファイルにコンパイルするところまでしかやらない。
  • 実際に.oを組み合わせて実行ファイルを作っているのはリンカ。
  • .oファイルは実行ファイル作成時に消されてしまう。
    • gcc に-cオプションをつけることで、リンカの処理を実行しないようにできる

リンカ適応度前適応後のバイナリを比べてみる

  • gcc test.c a.c b.c -o test
  • gcc -c test.c a.c b.c
$ ls -alt
total 72
-rw-r--r--  1   staff   748  7 16 19:13 b.o
-rw-r--r--  1   staff   748  7 16 19:13 a.o
-rw-r--r--  1   staff   672  7 16 19:13 test.o
-rwxr-xr-x  1   staff  8488  7 16 19:13 test
  • test.o:8488Byteのサイズはa.o + b.o + test.o = 2168Byteを大きくうわ待っている
$ hexdump -C test
00000000  cf fa ed fe 07 00 00 01  03 00 00 80 02 00 00 00  |................|
00000010  0f 00 00 00 b0 04 00 00  85 00 20 00 00 00 00 00  |.......... .....|
00000020  19 00 00 00 48 00 00 00  5f 5f 50 41 47 45 5a 45  |....H...__PAGEZE|
00000030  52 4f 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |RO..............|
00000040  00 00 00 00 01 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000060  00 00 00 00 00 00 00 00  19 00 00 00 d8 01 00 00  |................|
00000070  5f 5f 54 45 58 54 00 00  00 00 00 00 00 00 00 00  |__TEXT..........|
00000080  00 00 00 00 01 00 00 00  00 10 00 00 00 00 00 00  |................|
00000090  00 00 00 00 00 00 00 00  00 10 00 00 00 00 00 00  |................|
000000a0  07 00 00 00 05 00 00 00  05 00 00 00 00 00 00 00  |................|
000000b0  5f 5f 74 65 78 74 00 00  00 00 00 00 00 00 00 00  |__text..........|
000000c0  5f 5f 54 45 58 54 00 00  00 00 00 00 00 00 00 00  |__TEXT..........|
000000d0  20 0f 00 00 01 00 00 00  5d 00 00 00 00 00 00 00  | .......].......|
000000e0  20 0f 00 00 04 00 00 00  00 00 00 00 00 00 00 00  | ...............|
000000f0  00 04 00 80 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000100  5f 5f 73 74 75 62 73 00  00 00 00 00 00 00 00 00  |__stubs.........|
00000110  5f 5f 54 45 58 54 00 00  00 00 00 00 00 00 00 00  |__TEXT..........|
00000120  7e 0f 00 00 01 00 00 00  06 00 00 00 00 00 00 00  |~...............|
00000130  7e 0f 00 00 01 00 00 00  00 00 00 00 00 00 00 00  |~...............|
00000140  08 04 00 80 00 00 00 00  06 00 00 00 00 00 00 00  |................|
00000150  5f 5f 73 74 75 62 5f 68  65 6c 70 65 72 00 00 00  |__stub_helper...|
00000160  5f 5f 54 45 58 54 00 00  00 00 00 00 00 00 00 00  |__TEXT..........|
00000170  84 0f 00 00 01 00 00 00  1a 00 00 00 00 00 00 00  |................|
00000180  84 0f 00 00 02 00 00 00  00 00 00 00 00 00 00 00  |................|
00000190  00 04 00 80 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001a0  5f 5f 63 73 74 72 69 6e  67 00 00 00 00 00 00 00  |__cstring.......|
000001b0  5f 5f 54 45 58 54 00 00  00 00 00 00 00 00 00 00  |__TEXT..........|
000001c0  9e 0f 00 00 01 00 00 00  14 00 00 00 00 00 00 00  |................|
000001d0  9e 0f 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001e0  02 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001f0  5f 5f 75 6e 77 69 6e 64  5f 69 6e 66 6f 00 00 00  |__unwind_info...|
00000200  5f 5f 54 45 58 54 00 00  00 00 00 00 00 00 00 00  |__TEXT..........|
00000210  b4 0f 00 00 01 00 00 00  48 00 00 00 00 00 00 00  |........H.......|
00000220  b4 0f 00 00 02 00 00 00  00 00 00 00 00 00 00 00  |................|
00000230  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000240  19 00 00 00 e8 00 00 00  5f 5f 44 41 54 41 00 00  |........__DATA..|
00000250  00 00 00 00 00 00 00 00  00 10 00 00 01 00 00 00  |................|
00000260  00 10 00 00 00 00 00 00  00 10 00 00 00 00 00 00  |................|
00000270  00 10 00 00 00 00 00 00  07 00 00 00 03 00 00 00  |................|
00000280  02 00 00 00 00 00 00 00  5f 5f 6e 6c 5f 73 79 6d  |........__nl_sym|
00000290  62 6f 6c 5f 70 74 72 00  5f 5f 44 41 54 41 00 00  |bol_ptr.__DATA..|
000002a0  00 00 00 00 00 00 00 00  00 10 00 00 01 00 00 00  |................|
000002b0  10 00 00 00 00 00 00 00  00 10 00 00 03 00 00 00  |................|
000002c0  00 00 00 00 00 00 00 00  06 00 00 00 01 00 00 00  |................|
000002d0  00 00 00 00 00 00 00 00  5f 5f 6c 61 5f 73 79 6d  |........__la_sym|
000002e0  62 6f 6c 5f 70 74 72 00  5f 5f 44 41 54 41 00 00  |bol_ptr.__DATA..|
000002f0  00 00 00 00 00 00 00 00  10 10 00 00 01 00 00 00  |................|
00000300  08 00 00 00 00 00 00 00  10 10 00 00 03 00 00 00  |................|
00000310  00 00 00 00 00 00 00 00  07 00 00 00 03 00 00 00  |................|
00000320  00 00 00 00 00 00 00 00  19 00 00 00 48 00 00 00  |............H...|
00000330  5f 5f 4c 49 4e 4b 45 44  49 54 00 00 00 00 00 00  |__LINKEDIT......|
00000340  00 20 00 00 01 00 00 00  00 10 00 00 00 00 00 00  |. ..............|
00000350  00 20 00 00 00 00 00 00  28 01 00 00 00 00 00 00  |. ......(.......|
00000360  07 00 00 00 01 00 00 00  00 00 00 00 00 00 00 00  |................|
00000370  22 00 00 80 30 00 00 00  00 20 00 00 08 00 00 00  |"...0.... ......|
00000380  08 20 00 00 18 00 00 00  00 00 00 00 00 00 00 00  |. ..............|
00000390  20 20 00 00 10 00 00 00  30 20 00 00 40 00 00 00  |  ......0 ..@...|
000003a0  02 00 00 00 18 00 00 00  78 20 00 00 06 00 00 00  |........x ......|
000003b0  e8 20 00 00 40 00 00 00  0b 00 00 00 50 00 00 00  |. ..@.......P...|
000003c0  00 00 00 00 00 00 00 00  00 00 00 00 04 00 00 00  |................|
000003d0  04 00 00 00 02 00 00 00  00 00 00 00 00 00 00 00  |................|
000003e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000003f0  d8 20 00 00 04 00 00 00  00 00 00 00 00 00 00 00  |. ..............|
00000400  00 00 00 00 00 00 00 00  0e 00 00 00 20 00 00 00  |............ ...|
00000410  0c 00 00 00 2f 75 73 72  2f 6c 69 62 2f 64 79 6c  |..../usr/lib/dyl|
00000420  64 00 00 00 00 00 00 00  1b 00 00 00 18 00 00 00  |d...............|
00000430  01 a0 6d a0 dc d8 3b 93  b9 e4 c3 d8 18 65 02 c8  |..m...;......e..|
00000440  24 00 00 00 10 00 00 00  00 0d 0a 00 00 0d 0a 00  |$...............|
00000450  2a 00 00 00 10 00 00 00  00 00 00 00 00 00 00 00  |*...............|
00000460  28 00 00 80 18 00 00 00  20 0f 00 00 00 00 00 00  |(....... .......|
00000470  00 00 00 00 00 00 00 00  0c 00 00 00 38 00 00 00  |............8...|
00000480  18 00 00 00 02 00 00 00  04 32 e4 04 00 00 01 00  |.........2......|
00000490  2f 75 73 72 2f 6c 69 62  2f 6c 69 62 53 79 73 74  |/usr/lib/libSyst|
000004a0  65 6d 2e 42 2e 64 79 6c  69 62 00 00 00 00 00 00  |em.B.dylib......|
000004b0  26 00 00 00 10 00 00 00  70 20 00 00 08 00 00 00  |&.......p ......|
000004c0  29 00 00 00 10 00 00 00  78 20 00 00 00 00 00 00  |).......x ......|
000004d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000f20  55 48 89 e5 b0 00 e8 15  00 00 00 b0 00 e8 2e 00  |UH..............|
00000f30  00 00 31 c0 5d c3 90 90  90 90 90 90 90 90 90 90  |..1.]...........|
00000f40  55 48 89 e5 48 83 ec 10  48 8d 3d 4f 00 00 00 e8  |UH..H...H.=O....|
00000f50  2a 00 00 00 89 45 fc 48  83 c4 10 5d c3 90 90 90  |*....E.H...]....|
00000f60  55 48 89 e5 48 83 ec 10  48 8d 3d 38 00 00 00 e8  |UH..H...H.=8....|
00000f70  0a 00 00 00 89 45 fc 48  83 c4 10 5d c3 90 ff 25  |.....E.H...]...%|
00000f80  8c 00 00 00 4c 8d 1d 7d  00 00 00 41 53 ff 25 6d  |....L..}...AS.%m|
00000f90  00 00 00 90 68 00 00 00  00 e9 e6 ff ff ff 61 20  |....h.........a |
00000fa0  63 61 6c 6c 65 64 00 62  28 29 20 63 61 6c 6c 65  |called.b() calle|
00000fb0  64 00 00 00 01 00 00 00  1c 00 00 00 00 00 00 00  |d...............|
00000fc0  1c 00 00 00 00 00 00 00  1c 00 00 00 02 00 00 00  |................|
00000fd0  20 0f 00 00 34 00 00 00  34 00 00 00 7e 0f 00 00  | ...4...4...~...|
00000fe0  00 00 00 00 34 00 00 00  03 00 00 00 0c 00 01 00  |....4...........|
00000ff0  10 00 01 00 00 00 00 00  00 00 00 01 00 00 00 00  |................|
00001000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00001010  94 0f 00 00 01 00 00 00  00 00 00 00 00 00 00 00  |................|
00001020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00002000  11 22 10 51 00 00 00 00  11 40 64 79 6c 64 5f 73  |.".Q.....@dyld_s|
00002010  74 75 62 5f 62 69 6e 64  65 72 00 51 72 00 90 00  |tub_binder.Qr...|
00002020  72 10 11 40 5f 70 75 74  73 00 90 00 00 00 00 00  |r..@_puts.......|
00002030  00 01 5f 00 05 00 04 5f  6d 68 5f 65 78 65 63 75  |.._...._mh_execu|
00002040  74 65 5f 68 65 61 64 65  72 00 27 6d 61 69 6e 00  |te_header.'main.|
00002050  2b 61 00 30 62 00 35 02  00 00 00 03 00 a0 1e 00  |+a.0b.5.........|
00002060  03 00 c0 1e 00 03 00 e0  1e 00 00 00 00 00 00 00  |................|
00002070  a0 1e 20 20 00 00 00 00  02 00 00 00 0f 01 10 00  |..  ............|
00002080  00 00 00 00 01 00 00 00  16 00 00 00 0f 01 00 00  |................|
00002090  40 0f 00 00 01 00 00 00  19 00 00 00 0f 01 00 00  |@...............|
000020a0  60 0f 00 00 01 00 00 00  1c 00 00 00 0f 01 00 00  |`...............|
000020b0  20 0f 00 00 01 00 00 00  22 00 00 00 01 00 00 01  | .......".......|
000020c0  00 00 00 00 00 00 00 00  28 00 00 00 01 00 00 01  |........(.......|
000020d0  00 00 00 00 00 00 00 00  04 00 00 00 05 00 00 00  |................|
000020e0  00 00 00 40 04 00 00 00  20 00 5f 5f 6d 68 5f 65  |...@.... .__mh_e|
000020f0  78 65 63 75 74 65 5f 68  65 61 64 65 72 00 5f 61  |xecute_header._a|
00002100  00 5f 62 00 5f 6d 61 69  6e 00 5f 70 75 74 73 00  |._b._main._puts.|
00002110  64 79 6c 64 5f 73 74 75  62 5f 62 69 6e 64 65 72  |dyld_stub_binder|
00002120  00 00 00 00 00 00 00 00                           |........|
$ hexdump -C test.o
00000000  cf fa ed fe 07 00 00 01  03 00 00 00 01 00 00 00  |................|
00000010  04 00 00 00 b0 01 00 00  00 20 00 00 00 00 00 00  |......... ......|
00000020  19 00 00 00 38 01 00 00  00 00 00 00 00 00 00 00  |....8...........|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040  78 00 00 00 00 00 00 00  d0 01 00 00 00 00 00 00  |x...............|
00000050  78 00 00 00 00 00 00 00  07 00 00 00 07 00 00 00  |x...............|
00000060  03 00 00 00 00 00 00 00  5f 5f 74 65 78 74 00 00  |........__text..|
00000070  00 00 00 00 00 00 00 00  5f 5f 54 45 58 54 00 00  |........__TEXT..|
00000080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000090  16 00 00 00 00 00 00 00  d0 01 00 00 04 00 00 00  |................|
000000a0  48 02 00 00 02 00 00 00  00 04 00 80 00 00 00 00  |H...............|
000000b0  00 00 00 00 00 00 00 00  5f 5f 63 6f 6d 70 61 63  |........__compac|
000000c0  74 5f 75 6e 77 69 6e 64  5f 5f 4c 44 00 00 00 00  |t_unwind__LD....|
000000d0  00 00 00 00 00 00 00 00  18 00 00 00 00 00 00 00  |................|
000000e0  20 00 00 00 00 00 00 00  e8 01 00 00 03 00 00 00  | ...............|
000000f0  58 02 00 00 01 00 00 00  00 00 00 02 00 00 00 00  |X...............|
00000100  00 00 00 00 00 00 00 00  5f 5f 65 68 5f 66 72 61  |........__eh_fra|
00000110  6d 65 00 00 00 00 00 00  5f 5f 54 45 58 54 00 00  |me......__TEXT..|
00000120  00 00 00 00 00 00 00 00  38 00 00 00 00 00 00 00  |........8.......|
00000130  40 00 00 00 00 00 00 00  08 02 00 00 03 00 00 00  |@...............|
00000140  00 00 00 00 00 00 00 00  0b 00 00 68 00 00 00 00  |...........h....|
00000150  00 00 00 00 00 00 00 00  24 00 00 00 10 00 00 00  |........$.......|
00000160  00 0d 0a 00 00 00 00 00  02 00 00 00 18 00 00 00  |................|
00000170  60 02 00 00 03 00 00 00  90 02 00 00 10 00 00 00  |`...............|
00000180  0b 00 00 00 50 00 00 00  00 00 00 00 00 00 00 00  |....P...........|
00000190  00 00 00 00 01 00 00 00  01 00 00 00 02 00 00 00  |................|
000001a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000001d0  55 48 89 e5 b0 00 e8 00  00 00 00 b0 00 e8 00 00  |UH..............|
000001e0  00 00 31 c0 5d c3 00 00  00 00 00 00 00 00 00 00  |..1.]...........|
000001f0  16 00 00 00 00 00 00 01  00 00 00 00 00 00 00 00  |................|
00000200  00 00 00 00 00 00 00 00  14 00 00 00 00 00 00 00  |................|
00000210  01 7a 52 00 01 78 10 01  10 0c 07 08 90 01 00 00  |.zR..x..........|
00000220  24 00 00 00 1c 00 00 00  a8 ff ff ff ff ff ff ff  |$...............|
00000230  16 00 00 00 00 00 00 00  00 41 0e 10 86 02 43 0d  |.........A....C.|
00000240  06 00 00 00 00 00 00 00  0e 00 00 00 02 00 00 2d  |...............-|
00000250  07 00 00 00 01 00 00 2d  00 00 00 00 01 00 00 06  |.......-........|
00000260  01 00 00 00 0f 01 00 00  00 00 00 00 00 00 00 00  |................|
00000270  0a 00 00 00 01 00 00 00  00 00 00 00 00 00 00 00  |................|
00000280  07 00 00 00 01 00 00 00  00 00 00 00 00 00 00 00  |................|
00000290  00 5f 6d 61 69 6e 00 5f  62 00 5f 61 00 00 00 00  |._main._b._a....|
000002a0

ふむふむなるほど(無能)

結局意味がわからないので特に何も書くことがない。 とりあえずcatで連結するような単純なものではないらしい。

感想

リンカは結構単純な置換をやっているだけに見えるが、 ruiさんが

Cプリプロセッサの実装はかなり手間がかかる。

qiita.com

と書かれていたり

コンパイラの入門書で作成対象外になっていたりなどで

本書では、この4つの段階のうちプリプロセスを除いた、コンパイルアセンブル・リンクについてお話しします(普通のコンパイラを作ろうp8より)

作るとなると意外と難しいものなのかもしれない。

C言語の復習その6メモリについて

メモリ管理について学びました。malloc,free,reallocなど

コンパイルするときに、知識不足によるコンパイルエラーがかなり減りました。*1 またコンパイルエラー時に、何が間違っているかすぐわかるようになり、なぜ動いているのか・動かないのかわからないというソースがかなり減りました。

mallocについて

ローカル変数との違い

  • ローカル変数は確保できる合計サイズに限りがある
  • mallocの方が柔軟。
  • staticをつけた変数はデータセグメントに格納される
  • ではローカル変数は?
    • スタック領域に確保されます
      • ある程度の領域を抑えてスタックのベースアドレスから、
        • 下に伸びる
        • 上に伸びる
      • みたいな感じで空き容量を埋めていく感じです。
  • mallocではヒープ領域にメモリを確保する

    • heapの上限はない?

      この「ヒープ領域」には上限が決められていません。p223より

      • スタックはコンパイル、リンク時に大きさが決まるらしい。
  • mallocは失敗するとNULL=(((void*)0))が帰ってくる*2

mallocで戻ってきたアドレスを紛失しないこと

ローカル変数でmallocの返値アドレスを確保して、スコープを抜けるなどで噴出すると、 プログラム実行終了までに確保したアドレスは回収されない。

javaのようなメソッドエリア(静的領域とも)からの参照がなくなった参照を自動的に解放対象にする(ガベージコレクション)のような仕組みはない。

対策としては、

  • アドレスを管理している変数を関数の返値として呼び出し元に返す
  • グローバル変数で管理する

などがある。

free()について

  • mallocで確保した引数を与えるとそこを解放する
  • void free(void *ptr);
  • 宣言を見る限り
    • void* -> charのキャストはもちろんできるが、
    • char ->void* のキャストも可能なのか。
  • mallocで確保していないアドレスや解放済みのアドレスをfree()に渡すと実行時エラーになる
  • コンパイル時にわかりそうなもんだが。。。
p = (char *) malloc(strlen(str)+1);
void* vp = (void*) p;

free(p);
//free(vp);
// 間違えて、同じアドレスを指している変数を2回freeしてしまった。
// その時のエラーを記念に残しておく
// a.out(20725,0x7fffad00f380) malloc: *** error for object 0x7f8ebd402710: pointer being freed was not allocated
// *** set a breakpoint in malloc_error_break to debug
// Abort trap: 6

char localv = 'c';
//free(&localv);
//ローカル変数をfreeした結果。
// a.out(20787,0x7fffad00f380) malloc: *** error for object 0x7ffee5ed7ab7: pointer being freed was not allocated
// *** set a breakpoint in malloc_error_break to debug
// Abort trap: 6
  • freeをしないとヒープの領域を食いつぶしてメモリリークが発生する
    • 厄介なバグの一つ
    • malloc()で確保した領域は必ずfreeせよ!!!

realloc

void *realloc(void *ptr, size_t size);

  • 一度mallocした領域の拡張を行うことができる
  • ただしmallocで確保したサイズより大きいデータを書き込んでも普通に実行できてしまった。
  char *str1 = "hello";
  char *str2 = " world";

  p = malloc(strlen(str1)+1);

  strcpy(p,str1);
  puts(p);//hello

  //なぜか入れても抜いても同じ結果になる。
  //p = realloc(p,strlen(str1)+ strlen(str2) +1);

  strcat(p,str2);
  puts(p);//hello world;

  free(p);
  • 確保した領域の後ろのアドレスが空で、たまたま使えていたということ?
  • 保証されていない動作?ということだろうか。

  • mallocを2回続けて、1回目の領域に大きなデータを書いてみたが、バグらせることができなかった。

reallocやらないとどうなるか。

  • 複数回連続でmallocをして確保したアドレスをprintf()してみたが、アドレスは綺麗に順番で確保されているわけではないようだ

  • 以下は複数char型のポインタ変数のアドレスを確保して実験したソース

  char *vp1 = malloc(1);
  char *vp2 = malloc(1);
  char *vp3 = malloc(1);
  char *vp4 = malloc(1);
  char *vp5 = malloc(1);
  char *vp6 = malloc(1);

  //1バイトしか確保していない領域に複数バイト分の文字を書き込み
  //文字列リテラルはそれ自体が静的データへのアドレスなので以下のコードは実験としてふさわしくない
  //vp1 = "test";
  strcpy(vp1,"testtesttesttesttesttesttesttest");
  strcpy(vp2,"computercomputercomputercomputer");
  strcpy(vp3,"abcdeabcdeabcdeabcdeabcdeabcde");
  strcpy(vp4,"englishenglishenglishenglishenglish");
  strcpy(vp5,"boyboyboyboyboyboyboyboyboyboy");
  strcpy(vp6,"girlgirlgirlgirlgirlgirlgirlgirlgirl");


  puts(vp1);
  puts(vp2);
  puts(vp3);
  puts(vp4);
  puts(vp5);
  puts(vp6);
  //結果:データ破壊ができた
  // bcdeabcdeabcde
  // computercomputerabcdeabcdeabcdeabcdeabcdeabcde
  // abcdeabcdeabcdeabcdeabcdeabcde
  // englishenglishenboyboyboyboyboybgirlgirlgirlgirlgirlgirlgirlgirlgirl
  // boyboyboyboyboybgirlgirlgirlgirlgirlgirlgirlgirlgirl
  // girlgirlgirlgirlgirlgirlgirlgirlgirl


  printf("vp1 addr is %p\n",vp1);
  printf("vp1 value is %c\n",*vp1);

  printf("vp2 addr is %p\n",vp2);
  printf("vp2 value is %c\n",*vp2);

  printf("vp3 addr is %p\n",vp3);
  printf("vp3 value is %c\n",*vp3);

  printf("vp4 addr is %p\n",vp4);
  printf("vp4 value is %c\n",*vp4);

  printf("vp5 addr is %p\n",vp5);
  printf("vp5 value is %c\n",*vp5);

  printf("vp6 addr is %p\n",vp6);
  printf("vp6 value is %c\n",*vp6);

  //出力
  // vp1 addr is 0x7fc072402730
  // vp1 value is b
  // vp2 addr is 0x7fc072402710
  // vp2 value is c
  // vp3 addr is 0x7fc072402720
  // vp3 value is a
  // vp4 addr is 0x7fc072402750
  // vp4 value is e
  // vp5 addr is 0x7fc072402760
  // vp5 value is b
  // vp6 addr is 0x7fc072402770
  // vp6 value is g


  • 値の欠損、上書きなどが起こったが、想定したものと微妙に違う。
    • mallocは10バイト間隔を開けてアドレスを確保している
    • mallocが確保するアドレスはアドレスの並び順通りではない
    • 上の例だと
      • vp1 addr is 0x7fc072402730
      • vp2 addr is 0x7fc072402710
      • vp3 addr is 0x7fc072402720
      • vp4 addr is 0x7fc072402750
    • 最近のコンパイラバッファオーバーフロー対策でこういう細工がしてあるということを少し聞きかじったことがあるが、もしかしてそれなのでしょうか。
    • コンパイラのオプションでこの細工を無効化できたはずだが。

realloc実践

確保した領域より大きい文字列を書き込む前に、reallocすることで適切な動作を実現できた。

char *vp1 = malloc(1);
char *vp2 = malloc(1);
char *vp3 = malloc(1);
char *vp4 = malloc(1);
char *vp5 = malloc(1);
char *vp6 = malloc(1);

char* str1 = "testtesttesttesttesttesttesttest";
char* str2 = "computercomputercomputercomputer";
char* str3 = "abcdeabcdeabcdeabcdeabcdeabcde";
char* str4 = "englishenglishenglishenglishenglish";
char* str5 = "boyboyboyboyboyboyboyboyboyboy";
char* str6 = "girlgirlgirlgirlgirlgirlgirlgirlgirl";

vp1 = realloc(vp1,strlen(str1)+1);
vp2 = realloc(vp2,strlen(str2)+1);
vp3 = realloc(vp3,strlen(str3)+1);
vp4 = realloc(vp4,strlen(str4)+1);
vp5 = realloc(vp5,strlen(str5)+1);
vp6 = realloc(vp6,strlen(str6)+1);

strcpy(vp1,str1);
strcpy(vp2,str2);
strcpy(vp3,str3);
strcpy(vp4,str4);
strcpy(vp5,str5);
strcpy(vp6,str6);


puts(vp1);
puts(vp2);
puts(vp3);
puts(vp4);
puts(vp5);
puts(vp6);


printf("vp1 addr is %p\n",vp1);
printf("vp1 value is %c\n",*vp1);

printf("vp2 addr is %p\n",vp2);
printf("vp2 value is %c\n",*vp2);

printf("vp3 addr is %p\n",vp3);
printf("vp3 value is %c\n",*vp3);

printf("vp4 addr is %p\n",vp4);
printf("vp4 value is %c\n",*vp4);

printf("vp5 addr is %p\n",vp5);
printf("vp5 value is %c\n",*vp5);

printf("vp6 addr is %p\n",vp6);
printf("vp6 value is %c\n",*vp6);

//出力
// testtesttesttesttesttesttesttest
// computercomputercomputercomputer
// abcdeabcdeabcdeabcdeabcdeabcde
// englishenglishenglishenglishenglish
// boyboyboyboyboyboyboyboyboyboy
// girlgirlgirlgirlgirlgirlgirlgirlgirl
// vp1 addr is 0x7fa623402770
// vp1 value is t
// vp2 addr is 0x7fa6234027a0
// vp2 value is c
// vp3 addr is 0x7fa6234027d0
// vp3 value is a
// vp4 addr is 0x7fa623402810
// vp4 value is e
// vp5 addr is 0x7fa6234027f0
// vp5 value is b
// vp6 addr is 0x7fa623402710
// vp6 value is g

void型のポインタ

  • mallocの結果はvoid型のポインタとして帰ってくる
  • void *malloc(size_t size);
  • これは任意のポインタ型に代入(暗黙の型変換を伴う)可能になっている
  • もちろん明示的にキャストすることも可能
char *p;
char *str = "Getting memories!!";
p = (char *) malloc(strlen(str)+1);
//キャストしなくてもOK
//p = malloc(strlen(str)+1);
void* vp = (void*) p;
printf("void pointer type size is %d\n",sizeof vp);
strcpy(p,str);
puts(p);

//vp型のポインタ変数もputsに渡せるようだ。
puts(vp);
//putsは引数にchar型のポインタをとるので、渡った先で暗黙の型変換がされているものと思われる
//int puts(char * s);

Linuxmallocmmapについて

liunuxではmalloc()は以下のような動作になっている

  • mmap()関数で似たようなことができる
    • mmap関数ではページ単位でメモリを獲得する
    • malloc関数ではバイト単位で管理する
      • 内部ではmmapシステムコールを使ってページ単位で確保し、それをプールしている
      • malloc呼び出し時にプールから指定バイト数分切り出して渡している

Linuxの仕組みより

[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識

[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識

hariboteOSでは?

memmann_alloc(カーネル用のmalloc)

こちらは MEMMAN_ADDRというmammann_allocで割り当てる領域の開始アドレスがdefineで(0x003c0000)に指定されています。

api_malloc(アプリ用のmalloc)

mallocで割り当てるアドレスがどこから始まるかをhariboteOS用の実行ファイル.hrbファイルに書きこまれています。 あまり自信がありませんが、これはデータセグメントのベースからのオフセットアドレスだと思います。

感想

メモリリークは大変だと思うが、 発生した際に時間をかければ、問題箇所の調査ぐらいはできるくらいの知識はついた気がする。

今まではメモリリークの調査など絶対できそうになかったが、最近は結構自信がついてきた。(まだやったことないけど)

ただし、調査に時間はすごくかかりそうだ。

プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み

プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み

C言語の復習その5。関数ポインタについて

関数ポインタは「宣言方法」と「引数、返値があっていない適当に実装されたコードを読む」のがややこしいが、綺麗に書けばそこまで難解ではない。という認識です。

関数ポインタについて

  • 宣言 void(*p)()
    • 引数付き void(*p)(char *str)
  • *pを囲ってる()が若干気持ち悪い
    • これがないと演算子の優先度の都合上、ポインタ型の変数を返す関数になってしまう。
      • int *p()これはダメ。
    • ()で囲うことで演算子の優先順位を調整している
    • あくまで返値ではなく、関数自体のポインタですよ。という理解をするとわかりやすい

引数や戻り値は暗黙の型変換が絡んで来るとややこしい

この記事を書いた当初、gccコンパイルオプションを-W -Wallと書くべきところを-w -Wallと書いていまい、 警告を抑制するという馬鹿なことをやってしまいました。

※警告を最大限に出すようにオプション(-W -Wall)を与えて*1、悪いプログラミングの癖は強制するべきだと思っています。

    //引数なし、返値なしの関数の場合
    void (*fp)(void) = func1;//この宣言代入がもっともふさわしい
    fp();
    fp(2);//エラー void型の関数の引数にintを渡したため

    //関数ポインタの型を不適切なものにする
    int(*fp2)(int a) = func1;//警告がでる

    //関数ポインタの宣言時の引数の個数にふさわしく無い呼び出しをすると、
    //fp2();//コンパイルエラー
    // error: too few arguments to function call, expected 1,
    //   have 0
    // fp2();

    //関数ポインタの宣言時の引数にふさわしい引数を与えているが、代入した関数には引数はない
    //void func1();

    //一見不正なことをしているように見えるが、そもそも引数の指定が無い関数には引数を与えて呼び出すこともできる
    func1(1);//ただし警告は出る
    // other/typeConvert.c:44:12: warning: too many arguments in call to 'func1'
    // func1(1);
    // ~~~~~  ^

    //この挙動が気に入らないなら、関数ポインタの引数としてvoidを定義すれば良い。

    //ちなみに関数ポインタの宣言で引数が決められている場合は、代入する関数の引数がvoidであっても、
    //引数を与えて呼び出すことができてしまう。
    //void voidf(void)
    void (*fp3)(int a) = voidf;//ただしこの代入は警告が出る
    // warning: incompatible pointer types initializing
    //   'void (*)(int)' with an expression of type 'void (void)'
    //   [-Wincompatible-pointer-types]
    // void (*fp3)(int a) = voidf;
    //        ^             ~~~~~

    fp3(1);

    //防ぐには代入する関数の引数をvoidにして、関数ポインタの引数もvoidに、そしてコンパイラの警告オプションを最大にすることが重要。
・・・

//代入されている関数達
void func1(){
  puts("引数なしのfunc1が呼ばれました");
}
void func2(char *str){
  puts(str);
}
int intfunc(){
  return 3;
}
int intfunc2(int i){
  int result = i+3;
  printf("%d\n",result);
  return result;
}
void voidf(void){
  puts("引数を渡すとエラーになる関数が呼ばれました");
}
  • 暗黙の型変換を期待したコードでvoidが絡んでいると失敗する
  • 大体のルールはここに記載されている。

  • 関数ポインタと、ポインタ変数に代入する関数の

    • 引数
    • 戻り値
  • をきちんと一致するように宣言・代入し、コンパイルオプションで警告を細かく出せばこの問題に頭を悩ますことはないと思います。*2

    関数ポインタの配列

  • 宣言がちょっとわかりにくいけど、基本は同じなので特に解説なし。

//宣言
int(*p3[4])(char *output);

//代入
p3[0] = func1;
p3[1] = func2;
p3[2] = intfunc;
p3[3] = intfunc2;

//誤った使用
//以下のコンパイルはすべて失敗する
// p3[0]();
// p3[1]();
// p3[2]();
// p3[3]();

//コンパイル結果
// chap5/functionp.c:65:10: error: too few arguments to function call, expected 1,
//       have 0
//    p3[0]();
//    ~~~~~ ^
// chap5/functionp.c:66:10: error: too few arguments to function call, expected 1,
//       have 0
//    p3[1]();
//    ~~~~~ ^
// chap5/functionp.c:67:10: error: too few arguments to function call, expected 1,
//       have 0
//    p3[2]();
//    ~~~~~ ^
// chap5/functionp.c:68:10: error: too few arguments to function call, expected 1,
//       have 0
//    p3[3]();
//    ~~~~~ ^


//使用
p3[0]("123");//出力結果::引数なしのfunc1が呼ばれました
p3[1]("123");//出力結果:123
p3[2]("123");
p3[3]("123");//出力結果:152838021 多分不正に数値にキャストした文字列に3を足した結果。

・・・
//代入されている関数達
void func1(){
  puts("引数なしのfunc1が呼ばれました");
}
void func2(char *str){
  puts(str);
}
int intfunc(){
  return 3;
}
int intfunc2(int i){
  int result = i+3;
  printf("%d\n",result);
  return result;

}

補足、某所でいただいたアドバイスについて

引数なし、ではなくvoid型の引数というものがある。

DCL20-C. 引数を受け付けない関数の場合も必ず void を指定する

こちらを関数ポインタの型変数に宣言すれば、 引数をつけて読んだときにエラーになります。

   //void引数と関数ポインタの実験
   
   int (*fp1)(int a);
   //int voidf(void);
   fp1 = voidf;//代入可能
   //fp1();//エラー
   fp1(1);//実行可能
   //voidf(1);//コンパイルエラー

   int (*fp2)(void);
   fp2 = voidf;
   fp2();
   //fp2(10);//コンパイルエラー

暗黙の型変換は、コンパイラ依存のものも結構あり、gccで動くからといって油断はしないこと

INT36-C. ポインタから整数への変換、整数からポインタへの変換

プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み

プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み

*1:JSなら全てstrictモードにするなど

*2:パズルチックなコードや他人が書いたコードで型変換のルールを調べ直す日が来るかもしれません。