kakts-log

programming について調べたことを整理していきます

os.Exit()とdeferされた関数について

概要

os.Exit()を実行した際、プログラムが即座に終了するため、defer された関数が呼ばれない。
これについて整理します。

os.Exit()

ドキュメントを確認すると、defer された関数が呼ばれないことも明記されている

Exit causes the current program to exit with the given status code. Conventionally, code zero indicates success, non-zero an error. The program terminates immediately; deferred functions are not run.

For portability, the status code should be in the range [0, 125].

os.Exitの内部処理としては、システムコール exitを発行することにより、プログラムを終了させている。

  • os.Exitでは
// Exit causes the current program to exit with the given status code.
// Conventionally, code zero indicates success, non-zero an error.
// The program terminates immediately; deferred functions are not run.
//
// For portability, the status code should be in the range [0, 125].
func Exit(code int) {
    if code == 0 && testlog.PanicOnExit0() {
        // We were told to panic on calls to os.Exit(0).
        // This is used to fail tests that make an early
        // unexpected call to os.Exit(0).
        panic("unexpected call to os.Exit(0) during test")
    }

    // Inform the runtime that os.Exit is being called. If -race is
    // enabled, this will give race detector a chance to fail the
    // program (racy programs do not have the right to finish
    // successfully). If coverage is enabled, then this call will
    // enable us to write out a coverage data file.
    runtime_beforeExit(code)

    syscall.Exit(code)
}
  • 最終的にはruntimeパッケージのsyscall_exit()により、exitシステムコールが呼ばれる

https://github.com/golang/go/blob/master/src/runtime/runtime.go#L63-L67

func syscall_Exit(code int) {
    exit(int32(code))
}

log.Fatal時挙動について

os.Exitを内部で読んでいるlog.Fatal系の処理についてはどうでしょうか。
log.Fatalでは内部でos.Exitが呼ばれ、ログ出力と合わせてプログラムが終了します。
os.Exit()が呼ばれるため、上述した通り、deferで指定された関数は呼ばれずにプログラムが終了するので注意です。

https://cs.opensource.google/go/go/+/refs/tags/go1.21.2:src/log/log.go;l=284

JavaScript Primer改訂第2版の内容レビューに参加しました。

先月出版された「JavaScript Primer 改訂2版 迷わないための入門書」の内容レビューに関わらせていただきました。
先日出版社の方から、完成した書籍をご恵贈いただきました。ありがとうございます。

www.kadokawa.co.jp

efcl.info

第1版からの主なアップデートとして、ECMAScriptの新しいバージョン(ES2020, ES2021, ES2022)で取り入れられた機能を取り上げたり、Promise, Async Functionまわりについて書き直されたり、JavaScriptを初心者や、以前は触っていたが最近のアップデート内容のキャッチアップができていなかった方にも大変お勧めできます。 後半の応用編で、アプリを作成する章もあり、自分で手を動かしながら読んでいくと理解が深まるかと思います。

書籍だけでなく、web版もあるので是非気になったら読んでみてください。

jsprimer.net

JavaScript Primer 改訂2版 迷わないための入門書」の目次

はじめに
著者紹介
第1部 基本文法
第1章 JavaScriptとは
第2章 コメント
第3章 変数と宣言
第4章 値の評価と表示
第5章 データ型とリテラル
第6章 演算子
第7章 暗黙的な型変換
第8章 関数と宣言
第9章 文と式
第10章 条件分岐
第11章 ループと反復処理
第12章 オブジェクト
第13章 プロトタイプオブジェクト
第14章 配列
第15章 文字列
第16章 文字列とUnicode
第17章 ラッパーオブジェクト
第18章 関数とスコープ
第19章 関数とthis
第20章 クラス
第21章 例外処理
第22章 非同期処理: Promise/Async Function
第23章 Map/Set
第24章 JSON
第25章 Date
第26章 Math
第27章 ECMAScriptモジュール
第28章 ECMAScript
第2部 ユースケース
第29章 アプリケーション開発の準備
第30章 ユースケース: Ajax通信
第31章 ユースケース: Node.jsでCLIアプリケーション
第32章 ユースケース: Todoアプリケーション
付録A 参考リンク集

おわりに

一通り内容のレビューに関わらせてもらって、改めて最新のESの仕様についても自ら理解が深まりました。また、レビューをしてみて、改めて内容の密度の濃さを実感しており、 微力ながら、素晴らしい技術書のレビューに関わらせていただき、1エンジニアとして大変光栄です。

快くレビューの応募を受け入れていただいたazuさんはじめ出版社の方には大変感謝しております。

今後も何かしらで技術書の出版に関われたらとおもい、個人的に活動を続けて参ります。

RPMのgpgkeyの確認方法

概要

yumでのpackage installでPublic keyに関するエラーが出た際に、RPMのgpgkeyの確認方法が気になったのでまとめてみた

gpgkeyの確認方法

インスタンス内でインストールされているyum package用のgpgkeyの確認は下記方法でできる

 rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE}\t%{SUMMARY}\n'

rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE}\t%{SUMMARY}\n'
gpg-pubkey-f4a80eb5-53a7ff4b    gpg(CentOS-7 Key (CentOS 7 Official Signing Key) <security@centos.org>)
gpg-pubkey-352c64e5-52ae6884    gpg(Fedora EPEL (7) <epel@fedoraproject.org>)
gpg-pubkey-45f2c3d5-5e81efb9    gpg(Jenkins Project <jenkinsci-board@googlegroups.com>)
gpg-pubkey-dc6315a3-6091b7b3    gpg(Artifact Registry Repository Signer <artifact-registry-repository-signer@google.com>)
gpg-pubkey-3e1ba8d5-558ab6a8    gpg(Google Cloud Packages RPM Signing Key <gc-team@google.com>)

--qf '%{NAME}-%{VERSION}-%{RELEASE}\t%{SUMMARY}\n' で出力結果のフォーマットを指定できる

gpgkeyの削除方法

rpm -e ${gpg-pubkey-id}で削除できる 例えば、上記のJenkins用の gpgkeyを削除する場合は下記のようにする。

rpm -e gpg-pubkey-45f2c3d5-5e81efb9

これで該当のgpgkeyを削除できた

64ビットアーキテクチャにおいてインラインアセンブラでシステムコールを呼び出す

概要

独習アセンブラ8.4.1において、Cでインラインアセンブラによってシステムコールwriteを呼び出し、標準出力に文字列を出力するコードを実行させる際に、64bit環境では下記のエラーが出て実行できませんでした。
64bit環境で動作させるための方法を調べて整理します。

前提

 gcc --version
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
  • 今回利用したコード:

GitHub - kakts/assembly-language-training

Dockerでubuntuコンテナを立てて、そのコンテナ内で実行しています。

インラインアセンブラによってwriteシステムコールを呼び出す際にSegmentation Faultエラーが出る

独習アセンブラの著者のgithubリポジトリに今回実行しようとしたコードがあります。 asm/write.c at master · h-ohsaki/asm · GitHub

char *str = "Hello, World!\n";

int main (void) {
    asm ("movl str, %ecx");   // ECX ← 文字列のアドレス
    asm ("movl $14, %edx");   // EDX ← 文字列の長さ
    asm ("movl $4, %eax");    // システムコール 4 番は write
    asm ("movl $1, %ebx");    // 標準出力 (1)
    asm ("int $0x80"); // システムコール呼び出し
}

このコードをコンパイルし、実行ファイルを実行すると Segmentation faultエラーがでました。

$ gcc -g -no-pie -fno-pic -fomit-frame-pointer -o write write.c

user:~/src/inline$ ./write
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
Segmentation fault

1つずつコードを確かめて原因を探ると、システムコールを呼び出す割り込み命令実行時にエラーになっていることがわかりました。

asm("int $0x80");

原因

原因としては、実行する環境が64bitなのですが、上記コードにおいて、システムコール呼び出しに使っているint 0x80 という割り込みコードが32ビットに対応した方法だったことが原因でした。
64ビット環境では、int 0x80 を使わず、他の方法でシステムコールを実行する必要がありました。
システムコールの呼び出しにint $0x80を実行するのは、32bit x86アーキテクチャ用のもので、x86_64アーキテクチャでは、syscallという命令を使うことでシステムコールを呼び出せます。
呼び出す命令が変わるのと、システムコールを実行するにあたり、どのシステムコールを呼び出すか、また、引数の値をどのレジスタに値をセットするかの方法も異なります。

対処方法

x86_64ではint $0x80によってシステムコールが実行できないのを知り、さらに深掘りして調べたところ、下記の記事が見つかりました。 stackoverflow.com

一部抜粋しますが、64ビットのアーキテクチャではsyscallを使ってシステムコールを実行します。 実行にあたり利用するレジスタも、ビット数がことなるため32ビットのint 0x80を実行するときとは異なります。

32-bit code:

mov eax,4    ; In "int 0x80" style 4 means: write
mov ebx,1    ; ... and the first arg. is stored in ebx
mov ecx,esp  ; ... and the second arg. is stored in ecx
mov edx,1    ; ... and the third arg. is stored in edx
int 0x80
64-bit code:

mov rax,1    ; In "syscall" style 1 means: write
mov rdi,1    ; ... and the first arg. is stored in rdi (not rbx)
mov rsi,rsp  ; ... and the second arg. is stored in rsi (not rcx)
mov rdx,1    ; ... and the third arg. is stored in rdx
syscall

元々あったコードはコメントアウトし、64ビット環境でも実行できるコードに変更しました。

/**
 * @file write.c
 * 8.4.1 writeシステムコールの呼び出し
 */

char *str = "Hello,  World!\n";

int main(void) {

    /**
     * 32bitと64bitでシステムコールの呼び方がことなる
     * https://stackoverflow.com/questions/22503944/using-interrupt-0x80-on-64-bit-linux
     */
    // 32bit版
    // asm("movl str, %ecx"); // ECX<-文字列のアドレス
    // asm("movl $14, %edx"); // EDX<-文字列の長さ
    // asm("movl $4, %eax"); // システムコール4番はwrite
    // asm("movl $1, %ebx"); // 標準出力(1)
    // asm("int 0x80"); // システムコール呼び出し

    // 64bit(x86_64)版
    asm("mov $1, %rax"); // 1番はシステムコールwrite
    
    /**
     * システムコールを呼び出す際、
     * 第1引数、第2引数と、引数の順番に合わせて値をセットするレジスターが決まっている。
     * syscall実行時に、システムコールごとに所定のレジスタから値を取り出して実行する
     * 
     * 第1引数: rdi
     * 第2引数: rsi
     * 第3引数: rdx
     * ...
     */
    // writeの引数の値のセット
    asm("mov $1, %rdi"); // 第1引数 ファイルディスクリプタ stdout
    asm("mov str, %rsi"); // 第2引数 メッセージをwriteに渡す
    asm("mov $14, %rdx"); // rdx 文字列の長さ

    asm("syscall"); // システムコール実行
}

使用しているレジスタについて軽くまとめると以下になります。

このコードをコンパイルして、実行すると文字列が表示されるようになりました。

gcc -g -no-pie -fno-pic -fomit-frame-pointer -o write write.c
user@50596b389067:~/src/inline$ ./write
Hello,  World!

x86_64におけるシステムコールの番号

linux v5.0のコードですが、raxに指定するシステムコールの番号はlinuxソースコードで確認できます。 github.com

x86_64でのlinuxにおけるシステムコール実行時の処理

下記のコードでシステムコール実行時の処理をレジスタの扱いに関するコメントともに確認できます。
github.com

まとめ

今回は64ビットアーキテクチャで、C言語からインラインアセンブラによってシステムコールを実行する方法を整理しました。 64ビットにおけるシステムコール実行についての情報は、あらためて別の記事で整理したいと思います。

参考

stackoverflow.com

web.archive.org

Cでインラインアセンブラコードをgccでコンパイルする際に "relocation R_X86_64_32 against `.rodata' can not be used when making a PIE object;" というエラーが出た時の対処法

概要

GCCインラインアセンブラを利用して Cで書かれたコード内でアセンブリ言語のプログラムを埋め込み、コンパイルした場合に下記のエラーがでました gcc実行時に、内部でldコマンドが実行された時のエラーのようです。

$ gcc -fno-pic -fomit-frame-pointer inline.c -o inline
/usr/bin/ld: /tmp/ccEEoJFV.o: relocation R_X86_64_32 against `.rodata' can not be used when making a PIE object; recompile with -fPIE
collect2: error: ld returned 1 exit status
make: *** [Makefile:3: cmp-inline] Error 1

前提

user@079882db3e52:~/src/inline$ gcc --version
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

元のコード inline.c

#include <stdio.h>

int main(void) {
    int i = 123;
    asm("addl $456,%0" : "=r" (i) : "0" (i));
    printf("i = %d\n", i);
    return 0;
}

対処法

このエラーは -fno-picをつけてコンパイルした時に発生しており、下記の2つでの方法で対応できました

-fno-picを外す

gcc -fomit-frame-pointer inline.c -o inline
user@079882db3e52:~/src/inline$ ./inline
i = 579

これによりエラーが出ずに実行ファイルを生成できます

-no-pieをつける

gcc -no-pie -fno-pic -fomit-frame-pointer inline.c -o inline
user@079882db3e52:~/src/inline$ ./inline
i = 579

-no-pieをつけてもエラーが解消しました
参考: keens.github.io

【独習アセンブラ 4.5デバッガによるトレース】apple silicon macのローカルでgdbの代わりにlldbを使ったトレースの実行方法

概要

「独習アセンブラ」を読んでアセンブラを学んでいて、apple silicon(arm)のmacのローカル環境でコード書いてデバッグしています。

4.5「デバッガによるトレース」 で、gdbを使ってcのソースコードコンパイルした実行ファイルをもとにトレースを行いますが、arm版macでは書籍の内容の通りにトレースができませんでした。
arm版macではデバッグに使うgdbをインストールするのが面倒なので、本とは違う方法でデバッグする方法を模索し、lldbというデバッガを使ってうまいことできそうだったのでその手順をまとめます。
低レイヤーに関する理解が曖昧で、独学で学習を進めながらまとめている状態で、所々用語が間違っているかもですがご容赦ください。

前提

  • OS: macOS Monterey 12.5
  • Chip: Apple M1(Arm)
  • lldb: lldb-1316.0.9.46 Apple Swift version 5.6.1 (swiftlang-5.6.0.323.66 clang-1316.0.20.12)
  • gcc,clang: Apple clang version 13.1.6 (clang-1316.0.21.2.5) Target: arm64-apple-darwin21.6.0 Thread model: posix

手順

lldbの確認

lldbは元々インストールされていたのですが、念のため使えることを確認します。

which lldb
/usr/bin/lldb

lldb --version
lldb-1316.0.9.46
Apple Swift version 5.6.1 (swiftlang-5.6.0.323.66 clang-1316.0.20.12)

# lldbも実行できることを確認
lldb
(lldb) 

テスト用のCプログラムを作成する

「独習アセンブラ」のサポートページに記載されている、サンプルコードの通りに作成します。

github.com

ここでは、add.c という名前で保存します。

/**
 * 4.5.1 Cからアセンブリ言語への変換確認用
 */
int main(void) {
    register int i, j;
    i = 123;
    i = i + 1;
    j = 456;
    j = i + j;
    return j;
}

add.cのコンパイル

本に記載されている通りにgccコンパイルする場合、問題なく実行ファイルaddを生成できますが、lldbでのブレークポイントのセットの時にno locations (pending)というエラーが出てしまいます。

# コンパイルしてaddに実行ファイルを出力
gcc -fno-pic -fomit-frame-pointer -o add add.c;

# lldbによるデバッグ実行
# ここでは試しにadd.cの12行目にブレークポイントを貼ります。
(lldb) breakpoint set --file add.c --line 12
Breakpoint 1: no locations (pending).
WARNING:  Unable to resolve breakpoint to any actual locations.

gccでは -gを指定して、デバッグ可能な状態でコンパイルする必要があり、上記のgccコマンドに-gを追加することで問題なくブレークポイントをセットできました。

gcc --help
...
  -g                      Generate source-level debug information

qiita.com

# -gを追加してデバッグを有効にしたうえでコンパイル
gcc -g -fno-pic -fomit-frame-pointer -o add add.c;

# lldbを実行し、ブレークポイントを12, 13, 14行目に貼る。
lldb add
(lldb) target create "add"
Current executable set to '/Users/hoge/Documents/code/assembly-language-training/src/tr-regs/add' (arm64).
(lldb) breakpoint set --file add.c --line 12
Breakpoint 1: where = add`main + 40 at add.c:12:9, address = 0x0000000100003f30
(lldb) breakpoint set --file add.c --line 13
Breakpoint 2: where = add`main + 56 at add.c:13:23, address = 0x0000000100003f40
(lldb) breakpoint set --file add.c --line 14
Breakpoint 3: where = add`main + 84 at add.c:14:12, address = 0x0000000100003f5c
(lldb) r
Process 26586 launched: '/Users/hoge/Documents/code/assembly-language-training/src/tr-regs/add' (arm64)
Process 26586 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100003f30 add`main at add.c:12:9
   9        i = 123;
   10       i = i + 1;
   11       j = 456;
-> 12       j = i + j;
   13       printf("j: %d\n", j);
   14       return j;
   15   }
Target 0: (add) stopped.

補足
上記のブレークポイントに関するエラーについて色々調べたところ、gccでなくclangでも同じ感じでコンパイルできます。 github.com

clang -g -O0 add.c -o add -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer

gccの方では -fomit-frame-pointer をつけていましたが、clangでもgcc-fomit-frame-pointer と同じ設定をするには、 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointerの両方を指定する必要があるとのことだったのでそのまま指定しています。

stackoverflow.com

The problem ended up being that, on x86-64 Linux at least, Clang requires both -fno-omit-frame-pointer and -mno-omit-leaf-frame-pointer in order to get the same behavior that gcc gives with only -fno-omit-frame-pointer. See this LLVM bug: "Need both -fno-omit-frame-pointer and -mno-omit-leaf-frame-pointer to get a fp on linux on a leaf function"

これで一通り下記のことができるようになりました。 - lldbによるデバッグができる状態でコンパイルする - lldbで実行ファイルに対してブレークポイントを貼る

lldbによるデバッグ

前項でかるく説明しましたが、lldbによるデバッグを実行します。
ここではcのソースコードの所定の行にブレークポイントを貼り、デバッグモードで実行し、それぞれのブレークポイントで下記の内容を確認します。
- 実行ファイルから逆アセンブルし、アセンブリコードレベルでのデバッグ
- レジスターの内容の確認

前述でやったのと同じ感じで、lldbを起動し、ブレークポイントを張っていきます。

# lldbを実行し、ブレークポイントを12, 13, 14行目に貼る。
lldb add
(lldb) target create "add"
Current executable set to '/Users/hoge/Documents/code/assembly-language-training/src/tr-regs/add' (arm64).
(lldb) breakpoint set --file add.c --line 12
Breakpoint 1: where = add`main + 40 at add.c:12:9, address = 0x0000000100003f30
(lldb) breakpoint set --file add.c --line 13
Breakpoint 2: where = add`main + 56 at add.c:13:23, address = 0x0000000100003f40
(lldb) breakpoint set --file add.c --line 14
Breakpoint 3: where = add`main + 84 at add.c:14:12, address = 0x0000000100003f5c
(lldb) r
Process 26586 launched: '/Users/hoge/Documents/code/assembly-language-training/src/tr-regs/add' (arm64)
Process 26586 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100003f30 add`main at add.c:12:9
   9        i = 123;
   10       i = i + 1;
   11       j = 456;
-> 12       j = i + j;
   13       printf("j: %d\n", j);
   14       return j;
   15   }
Target 0: (add) stopped.

ブレークポイントを貼る

下記のように、breakpoint set を使って、ソースコードのファイルと行数を指定してブレークポイントを貼ることができます。

breakpoint set --file add.c --line 12

breakpoinは、エイリアスとしてb でも使えます。
使い方は、help bで見ることができ、上記と同じ内容でもっと簡潔に書くことができます。

breakpoint        -- Commands for operating on breakpoints (see 'help b' for shorthand.)
(lldb) help b
Set a breakpoint using one of several shorthand formats.  Expects 'raw' input (see 'help raw-input'.)

Syntax: 
_regexp-break <filename>:<linenum>:<colnum>
              main.c:12:21          // Break at line 12 and column 21 of main.c

下記のように簡単に書けます。

(lldb) b add.c:12
Breakpoint 7: where = add`main + 40 at add.c:12:9, address = 0x0000000100003f30
(lldb) b add.c:13
Breakpoint 8: where = add`main + 56 at add.c:13:23, address = 0x0000000100003f40
(lldb) b add.c:14
Breakpoint 9: where = add`main + 84 at add.c:14:12, address = 0x0000000100003f5c

デバッグの実行

ここで、ブレークポイントを設定したので、デバッグの実行をしていきます。 rを使ってデバッグ実行を開始できます。

  r         -- Launch the executable in the debugger.

ブレークポイントを貼った状態で実行します。

# ブレークポイント
...
(lldb) b add.c:14
Breakpoint 11: where = add`main + 84 at add.c:14:12, address = 0x0000000100003f5c

# デバッグ実行
(lldb) r
There is a running process, kill it and restart?: [Y/n] y
Process 27285 exited with status = 9 (0x00000009) 
Process 27388 launched: '/Users/hoge/Documents/code/assembly-language-training/src/tr-regs/add' (arm64)
Process 27388 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 4.1 7.1
    frame #0: 0x0000000100003f30 add`main at add.c:12:9
   9           i = 123;
   10          i = i + 1;
   11          j = 456;
-> 12       j = i + j;
   13          printf("j: %d\n", j);
   14          return j;
   15      }
Target 0: (add) stopped.
(lldb) 

12, 13 ,14行目に貼っているので、最初のブレークポイントで止まっている状態です。

レジスターの状態の確認

この状態で、register read を実行することで、ブレークポイントで止まっている位置でのレジスタの状態を確認できます。 ここでは先ほどと重複しますが、 12行目でのレジスタの状態を確認しています。
11行目で j = 456 となっていますが、x8のレジスタにjにセットされている456(16進数で1C8)の値があることが確認できます。

(lldb) r
There is a running process, kill it and restart?: [Y/n] y
Process 27285 exited with status = 9 (0x00000009) 
Process 27388 launched: '/Users/hoge/Documents/code/assembly-language-training/src/tr-regs/add' (arm64)
Process 27388 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 4.1 7.1
    frame #0: 0x0000000100003f30 add`main at add.c:12:9
   9           i = 123;
   10          i = i + 1;
   11          j = 456;
-> 12       j = i + j;
   13          printf("j: %d\n", j);
   14          return j;
   15      }
Target 0: (add) stopped.
(lldb) register read
General Purpose Registers:
        x0 = 0x0000000000000001
        x1 = 0x000000016fdff538
        x2 = 0x000000016fdff548
        x3 = 0x000000016fdff6b0
        x4 = 0x0000000000000000
        x5 = 0x0000000000000000
        x6 = 0x0000000000000000
        x7 = 0x0000000000000000
        x8 = 0x00000000000001c8 # <- ここで456の値がセットされている。
        x9 = 0x0000000000000002
       x10 = 0x0000000000000000
       x11 = 0x0000000000000002
       x12 = 0x0000000000000002
       x13 = 0x0000000000000000
       x14 = 0x0000000000000008
       x15 = 0x0000000000000000
       x16 = 0x0000000300033088
       x17 = 0x6ae100016fdfe7d0
       x18 = 0x0000000000000000
       x19 = 0x00000001000c0060
       x20 = 0x0000000100003f08  add`main at add.c:7
       x21 = 0x000000010006c070  dyld`dyld4::sConfigBuffer
       x22 = 0x0000000000000000
       x23 = 0x0000000000000000
       x24 = 0x0000000000000000
       x25 = 0x0000000000000000
       x26 = 0x0000000000000000
       x27 = 0x0000000000000000
       x28 = 0x0000000000000000
        fp = 0x000000016fdff510
        lr = 0x000000010001108c  dyld`start + 520
        sp = 0x000000016fdff3a0
        pc = 0x0000000100003f30  add`main + 40 at add.c:12:9
      cpsr = 0x60001000

実行中プログラムを逆アセンブルしたアセンブルコードを表示する

先ほどはcのソースファイルレベルでどこでブレークされているかを確認できましたが、この実行ファイルを逆アセンブリしたアセンブルコードレベルでどの状態にいるかを確認できます。
lldbのdi コマンドで確認ができます。

  di        -- Disassemble specified instructions in the current target.  Defaults to the current function
               for the current thread and stack frame.

ここでアセンブリコードを確認します。

(lldb) di
add`main:
    0x100003f08 <+0>:  sub    sp, sp, #0x30
    0x100003f0c <+4>:  stp    x29, x30, [sp, #0x20]
    0x100003f10 <+8>:  str    wzr, [sp, #0x1c]
    0x100003f14 <+12>: mov    w8, #0x7b
    0x100003f18 <+16>: str    w8, [sp, #0x18]
    0x100003f1c <+20>: ldr    w8, [sp, #0x18]
    0x100003f20 <+24>: add    w8, w8, #0x1
    0x100003f24 <+28>: str    w8, [sp, #0x18]
    0x100003f28 <+32>: mov    w8, #0x1c8
    0x100003f2c <+36>: str    w8, [sp, #0x14]
->  0x100003f30 <+40>: ldr    w8, [sp, #0x18]
    0x100003f34 <+44>: ldr    w9, [sp, #0x14]
    0x100003f38 <+48>: add    w8, w8, w9
    0x100003f3c <+52>: str    w8, [sp, #0x14]
    0x100003f40 <+56>: ldr    w9, [sp, #0x14]
    0x100003f44 <+60>: mov    x8, x9
    0x100003f48 <+64>: adrp   x0, 0
    0x100003f4c <+68>: add    x0, x0, #0xf78            ; "j: %d\n"
    0x100003f50 <+72>: mov    x9, sp
    0x100003f54 <+76>: str    x8, [x9]
    0x100003f58 <+80>: bl     0x100003f6c               ; symbol stub for: printf
    0x100003f5c <+84>: ldr    w0, [sp, #0x14]
    0x100003f60 <+88>: ldp    x29, x30, [sp, #0x20]
    0x100003f64 <+92>: add    sp, sp, #0x30
    0x100003f68 <+96>: ret    
(lldb) 

lldb上では 左側に-> が表示されている行が、現在のプログラムの位置となります。

ソースコード上の位置と、アセンブリコードの位置を確認してみます。 cでは、 j = i + j という処理をしているところとなります。

-> 12         j = i + j;

ここでは、ldrというオペコードで、w8, w9にそれぞれi と jの値を読み込み、add w8, w8, w9 で、w8とw9の値を足した結果をw8レジスタに格納することで j = i + j の処理を表しています。

->  0x100003f30 <+40>: ldr    w8, [sp, #0x18]
    0x100003f34 <+44>: ldr    w9, [sp, #0x14]
    0x100003f38 <+48>: add    w8, w8, w9

メモとして、オペコードは下記のページで確認できます。 - ARM LDR - ARM ADD

ここで、ブレークポイントで止まった箇所で下記2つを確認できました。 - ブレークポイントでのレジスタの状態 - 実行ファイルを逆アセンブルし、アセンブリコードレベルでどの処理を実行しているかの確認

次のブレークポイントまで処理を進める。

ここからは、次のブレークポイントへ処理を進めていきます。
下記の2つの方法で処理を進めることができます。

1.next, n : ソースコードレベルで次のブレークポイントまで進める(Cのソースコードの行数ごとに進める)
2. nexti , ni : 実際のアセンブリコードレベルで次のブレークポイントまで進める

  n         -- Source level single step, stepping over calls.  Defaults to current thread unless specified.
  next      -- Source level single step, stepping over calls.  Defaults to current thread unless specified.
  nexti     -- Instruction level single step, stepping over calls.  Defaults to current thread unless
               specified.
  ni        -- Instruction level single step, stepping over calls.  Defaults to current thread unless
               specified.

2では、前項で説明したように、1で設定したCのソースコードの各行ごとに、アセンブリコードでは複数の処理となる可能性があるため、アセンブリコードレベルで1つ1つ処理を進めていく形になります。
例: 前項でj = i + j という行は、アセンブリコードレベルではldr, ldr, add と3つのオペコードに分割されているため3ステップになること。

ここではアセンブリコードレベルで、どのように処理を進むかを確認したいため、12行目のj = i + j が終わるところまでnextiを実行して1つずつ処理を進めていきます。

(lldb) nexti
Process 27388 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over
    frame #0: 0x0000000100003f34 add`main at add.c:12:13
   9           i = 123;
   10          i = i + 1;
   11          j = 456;
-> 12       j = i + j;
   13          printf("j: %d\n", j);
   14          return j;
   15      }
Target 0: (add) stopped.
(lldb) di
add`main:
    0x100003f08 <+0>:  sub    sp, sp, #0x30
    0x100003f0c <+4>:  stp    x29, x30, [sp, #0x20]
    0x100003f10 <+8>:  str    wzr, [sp, #0x1c]
    0x100003f14 <+12>: mov    w8, #0x7b
    0x100003f18 <+16>: str    w8, [sp, #0x18]
    0x100003f1c <+20>: ldr    w8, [sp, #0x18]
    0x100003f20 <+24>: add    w8, w8, #0x1
    0x100003f24 <+28>: str    w8, [sp, #0x18]
    0x100003f28 <+32>: mov    w8, #0x1c8
    0x100003f2c <+36>: str    w8, [sp, #0x14]
    0x100003f30 <+40>: ldr    w8, [sp, #0x18]
->  0x100003f34 <+44>: ldr    w9, [sp, #0x14]
    0x100003f38 <+48>: add    w8, w8, w9
    0x100003f3c <+52>: str    w8, [sp, #0x14]
    0x100003f40 <+56>: ldr    w9, [sp, #0x14]
    0x100003f44 <+60>: mov    x8, x9
    0x100003f48 <+64>: adrp   x0, 0
    0x100003f4c <+68>: add    x0, x0, #0xf78            ; "j: %d\n"
    0x100003f50 <+72>: mov    x9, sp
    0x100003f54 <+76>: str    x8, [x9]
    0x100003f58 <+80>: bl     0x100003f6c               ; symbol stub for: printf
    0x100003f5c <+84>: ldr    w0, [sp, #0x14]
    0x100003f60 <+88>: ldp    x29, x30, [sp, #0x20]
    0x100003f64 <+92>: add    sp, sp, #0x30
    0x100003f68 <+96>: ret    
(lldb) register read
General Purpose Registers:
        x0 = 0x0000000000000001
        x1 = 0x000000016fdff538
        x2 = 0x000000016fdff548
        x3 = 0x000000016fdff6b0
        x4 = 0x0000000000000000
        x5 = 0x0000000000000000
        x6 = 0x0000000000000000
        x7 = 0x0000000000000000
        x8 = 0x000000000000007c
        x9 = 0x0000000000000002
       x10 = 0x0000000000000000
       x11 = 0x0000000000000002
       x12 = 0x0000000000000002
       x13 = 0x0000000000000000
       x14 = 0x0000000000000008
       x15 = 0x0000000000000000
       x16 = 0x0000000300033088
       x17 = 0x6ae100016fdfe7d0
       x18 = 0x0000000000000000
       x19 = 0x00000001000c0060
       x20 = 0x0000000100003f08  add`main at add.c:7
       x21 = 0x000000010006c070  dyld`dyld4::sConfigBuffer
       x22 = 0x0000000000000000
       x23 = 0x0000000000000000
       x24 = 0x0000000000000000
       x25 = 0x0000000000000000
       x26 = 0x0000000000000000
       x27 = 0x0000000000000000
       x28 = 0x0000000000000000
        fp = 0x000000016fdff510
        lr = 0x000000010001108c  dyld`start + 520
        sp = 0x000000016fdff3a0
        pc = 0x0000000100003f34  add`main + 44 at add.c:12:13
      cpsr = 0x60001000

(lldb) nexti
Process 27388 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over
    frame #0: 0x0000000100003f38 add`main at add.c:12:11
   9           i = 123;
   10          i = i + 1;
   11          j = 456;
-> 12       j = i + j;
   13          printf("j: %d\n", j);
   14          return j;
   15      }
Target 0: (add) stopped.
(lldb) di
add`main:
    0x100003f08 <+0>:  sub    sp, sp, #0x30
    0x100003f0c <+4>:  stp    x29, x30, [sp, #0x20]
    0x100003f10 <+8>:  str    wzr, [sp, #0x1c]
    0x100003f14 <+12>: mov    w8, #0x7b
    0x100003f18 <+16>: str    w8, [sp, #0x18]
    0x100003f1c <+20>: ldr    w8, [sp, #0x18]
    0x100003f20 <+24>: add    w8, w8, #0x1
    0x100003f24 <+28>: str    w8, [sp, #0x18]
    0x100003f28 <+32>: mov    w8, #0x1c8
    0x100003f2c <+36>: str    w8, [sp, #0x14]
    0x100003f30 <+40>: ldr    w8, [sp, #0x18]
    0x100003f34 <+44>: ldr    w9, [sp, #0x14]
->  0x100003f38 <+48>: add    w8, w8, w9
    0x100003f3c <+52>: str    w8, [sp, #0x14]
    0x100003f40 <+56>: ldr    w9, [sp, #0x14]
    0x100003f44 <+60>: mov    x8, x9
    0x100003f48 <+64>: adrp   x0, 0
    0x100003f4c <+68>: add    x0, x0, #0xf78            ; "j: %d\n"
    0x100003f50 <+72>: mov    x9, sp
    0x100003f54 <+76>: str    x8, [x9]
    0x100003f58 <+80>: bl     0x100003f6c               ; symbol stub for: printf
    0x100003f5c <+84>: ldr    w0, [sp, #0x14]
    0x100003f60 <+88>: ldp    x29, x30, [sp, #0x20]
    0x100003f64 <+92>: add    sp, sp, #0x30
    0x100003f68 <+96>: ret    
(lldb) register read
General Purpose Registers:
        x0 = 0x0000000000000001
        x1 = 0x000000016fdff538
        x2 = 0x000000016fdff548
        x3 = 0x000000016fdff6b0
        x4 = 0x0000000000000000
        x5 = 0x0000000000000000
        x6 = 0x0000000000000000
        x7 = 0x0000000000000000
        x8 = 0x000000000000007c
        x9 = 0x00000000000001c8
       x10 = 0x0000000000000000
       x11 = 0x0000000000000002
       x12 = 0x0000000000000002
       x13 = 0x0000000000000000
       x14 = 0x0000000000000008
       x15 = 0x0000000000000000
       x16 = 0x0000000300033088
       x17 = 0x6ae100016fdfe7d0
       x18 = 0x0000000000000000
       x19 = 0x00000001000c0060
       x20 = 0x0000000100003f08  add`main at add.c:7
       x21 = 0x000000010006c070  dyld`dyld4::sConfigBuffer
       x22 = 0x0000000000000000
       x23 = 0x0000000000000000
       x24 = 0x0000000000000000
       x25 = 0x0000000000000000
       x26 = 0x0000000000000000
       x27 = 0x0000000000000000
       x28 = 0x0000000000000000
        fp = 0x000000016fdff510
        lr = 0x000000010001108c  dyld`start + 520
        sp = 0x000000016fdff3a0
        pc = 0x0000000100003f38  add`main + 48 at add.c:12:11
      cpsr = 0x60001000

(lldb) nexti
Process 27388 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over
    frame #0: 0x0000000100003f3c add`main at add.c:12:7
   9           i = 123;
   10          i = i + 1;
   11          j = 456;
-> 12       j = i + j;
   13          printf("j: %d\n", j);
   14          return j;
   15      }
Target 0: (add) stopped.
(lldb) register read
General Purpose Registers:
        x0 = 0x0000000000000001
        x1 = 0x000000016fdff538
        x2 = 0x000000016fdff548
        x3 = 0x000000016fdff6b0
        x4 = 0x0000000000000000
        x5 = 0x0000000000000000
        x6 = 0x0000000000000000
        x7 = 0x0000000000000000
        x8 = 0x0000000000000244
        x9 = 0x00000000000001c8
       x10 = 0x0000000000000000
       x11 = 0x0000000000000002
       x12 = 0x0000000000000002
       x13 = 0x0000000000000000
       x14 = 0x0000000000000008
       x15 = 0x0000000000000000
       x16 = 0x0000000300033088
       x17 = 0x6ae100016fdfe7d0
       x18 = 0x0000000000000000
       x19 = 0x00000001000c0060
       x20 = 0x0000000100003f08  add`main at add.c:7
       x21 = 0x000000010006c070  dyld`dyld4::sConfigBuffer
       x22 = 0x0000000000000000
       x23 = 0x0000000000000000
       x24 = 0x0000000000000000
       x25 = 0x0000000000000000
       x26 = 0x0000000000000000
       x27 = 0x0000000000000000
       x28 = 0x0000000000000000
        fp = 0x000000016fdff510
        lr = 0x000000010001108c  dyld`start + 520
        sp = 0x000000016fdff3a0
        pc = 0x0000000100003f3c  add`main + 52 at add.c:12:7
      cpsr = 0x60001000

(lldb) nexti
Process 27388 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1 5.1 8.1
    frame #0: 0x0000000100003f40 add`main at add.c:13:23
   10          i = i + 1;
   11          j = 456;
   12          j = i + j;
-> 13       printf("j: %d\n", j);
   14          return j;
   15      }
Target 0: (add) stopped.
(lldb) di
add`main:
    0x100003f08 <+0>:  sub    sp, sp, #0x30
    0x100003f0c <+4>:  stp    x29, x30, [sp, #0x20]
    0x100003f10 <+8>:  str    wzr, [sp, #0x1c]
    0x100003f14 <+12>: mov    w8, #0x7b
    0x100003f18 <+16>: str    w8, [sp, #0x18]
    0x100003f1c <+20>: ldr    w8, [sp, #0x18]
    0x100003f20 <+24>: add    w8, w8, #0x1
    0x100003f24 <+28>: str    w8, [sp, #0x18]
    0x100003f28 <+32>: mov    w8, #0x1c8
    0x100003f2c <+36>: str    w8, [sp, #0x14]
    0x100003f30 <+40>: ldr    w8, [sp, #0x18]
    0x100003f34 <+44>: ldr    w9, [sp, #0x14]
    0x100003f38 <+48>: add    w8, w8, w9
    0x100003f3c <+52>: str    w8, [sp, #0x14]
->  0x100003f40 <+56>: ldr    w9, [sp, #0x14]
    0x100003f44 <+60>: mov    x8, x9
    0x100003f48 <+64>: adrp   x0, 0
    0x100003f4c <+68>: add    x0, x0, #0xf78            ; "j: %d\n"
    0x100003f50 <+72>: mov    x9, sp
    0x100003f54 <+76>: str    x8, [x9]
    0x100003f58 <+80>: bl     0x100003f6c               ; symbol stub for: printf
    0x100003f5c <+84>: ldr    w0, [sp, #0x14]
    0x100003f60 <+88>: ldp    x29, x30, [sp, #0x20]
    0x100003f64 <+92>: add    sp, sp, #0x30
    0x100003f68 <+96>: ret    
(lldb) di
add`main:
    0x100003f08 <+0>:  sub    sp, sp, #0x30
    0x100003f0c <+4>:  stp    x29, x30, [sp, #0x20]
    0x100003f10 <+8>:  str    wzr, [sp, #0x1c]
    0x100003f14 <+12>: mov    w8, #0x7b
    0x100003f18 <+16>: str    w8, [sp, #0x18]
    0x100003f1c <+20>: ldr    w8, [sp, #0x18]
    0x100003f20 <+24>: add    w8, w8, #0x1
    0x100003f24 <+28>: str    w8, [sp, #0x18]
    0x100003f28 <+32>: mov    w8, #0x1c8
    0x100003f2c <+36>: str    w8, [sp, #0x14]
    0x100003f30 <+40>: ldr    w8, [sp, #0x18]
    0x100003f34 <+44>: ldr    w9, [sp, #0x14]
    0x100003f38 <+48>: add    w8, w8, w9
    0x100003f3c <+52>: str    w8, [sp, #0x14]
->  0x100003f40 <+56>: ldr    w9, [sp, #0x14]
    0x100003f44 <+60>: mov    x8, x9
    0x100003f48 <+64>: adrp   x0, 0
    0x100003f4c <+68>: add    x0, x0, #0xf78            ; "j: %d\n"
    0x100003f50 <+72>: mov    x9, sp
    0x100003f54 <+76>: str    x8, [x9]
    0x100003f58 <+80>: bl     0x100003f6c               ; symbol stub for: printf
    0x100003f5c <+84>: ldr    w0, [sp, #0x14]
    0x100003f60 <+88>: ldp    x29, x30, [sp, #0x20]
    0x100003f64 <+92>: add    sp, sp, #0x30
    0x100003f68 <+96>: ret    
(lldb) register read
General Purpose Registers:
        x0 = 0x0000000000000001
        x1 = 0x000000016fdff538
        x2 = 0x000000016fdff548
        x3 = 0x000000016fdff6b0
        x4 = 0x0000000000000000
        x5 = 0x0000000000000000
        x6 = 0x0000000000000000
        x7 = 0x0000000000000000
        x8 = 0x0000000000000244
        x9 = 0x00000000000001c8
       x10 = 0x0000000000000000
       x11 = 0x0000000000000002
       x12 = 0x0000000000000002
       x13 = 0x0000000000000000
       x14 = 0x0000000000000008
       x15 = 0x0000000000000000
       x16 = 0x0000000300033088
       x17 = 0x6ae100016fdfe7d0
       x18 = 0x0000000000000000
       x19 = 0x00000001000c0060
       x20 = 0x0000000100003f08  add`main at add.c:7
       x21 = 0x000000010006c070  dyld`dyld4::sConfigBuffer
       x22 = 0x0000000000000000
       x23 = 0x0000000000000000
       x24 = 0x0000000000000000
       x25 = 0x0000000000000000
       x26 = 0x0000000000000000
       x27 = 0x0000000000000000
       x28 = 0x0000000000000000
        fp = 0x000000016fdff510
        lr = 0x000000010001108c  dyld`start + 520
        sp = 0x000000016fdff3a0
        pc = 0x0000000100003f40  add`main + 56 at add.c:13:23
      cpsr = 0x60001000

ここで、途中のところでレジスタのところを抜粋すると、 x80x7c(10進数で124)x90x1c8(10進数で456 となっていることがわかります。

...
        x8 = 0x000000000000007c
        x9 = 0x00000000000001c8
...

そして、j = i + j の計算が終わると、x8にその計算結果である0x244(10進数でいうと580)が格納されているのがわかります。
オペコードaddにより、x8の値のみ変わりますが、x9の値は変更がないのでそのままとなっていることが確認できます。

        x8 = 0x0000000000000244
        x9 = 0x00000000000001c8

今回やっていないこと

今回はlldbの使い方の簡単な整理が目的のため、下記の項目については取り掛かっていないですが、別途時間があればまとめようかと思います。

シンボリックリンク一覧の中から、指定したファイルを参照しているものを探す

概要

下記のように、a.txtのファイルを実体とし、それを参照したシンボリックリンクが2つ(b.txt, t/c.txt)あるとする。

この場合、a.txtを参照したシンボリックリンクを見つけたい場合、findコマンドを使うことで実現できます。 - b.txt - t/c.txt

$tree
.
├── a.txt
├── b.txt -> a.txt
└── t
    └── c.txt -> a.txt

ここでは簡単にするためにシンプルなファイル構造にしていて、ls コマンドでも見つけれますが、本当はいろんなところにシンボリックリンクが散らばっている場合に有効です。

コマンド

hoge.txtというファイルを参照しているシンボリックリンクをルート配下から探す場合は、下記コマンドで実現できます。

find / -type l -ls | grep hoge.txt

方法

所定ディレクトリ配下のシンボリックリンクを表示させる

findコマンドで、所定のディレクトリ配下のシンボリックリンクを表示するには、 find / -type l で可能です。

-type に lを指定すると、シンボリックリンクファイルを探すように設定できます。 manによってドキュメントを見てみると、下記のようになっています。ディレクトリや通常ファイル、FIFOなど、ファイルタイプを指定できます。

man find
     -type t
             True if the file is of the specified type.  Possible file types are as follows:

             b       block special
             c       character special
             d       directory
             f       regular file
             l       symbolic link
             p       FIFO
             s       socket
➜  misc find . -type l
./t/c.txt
./b.txt

ただし、ここではシンボリックリンクがどこを参照しているかわかりません。

表示されたシンボリックリンクの詳細を表示したい

ここで、見つかったファイルの情報を表示するには -lsオプションを付与します。

     -ls     This primary always evaluates to true.  The following information for the current file is written to standard output: its inode number, size in
             512-byte blocks, file permissions, number of hard links, owner, group, size in bytes, last modification time, and pathname.  If the file is a
             block or character special file, the device number will be displayed instead of the size in bytes.  If the file is a symbolic link, the pathname
             of the linked-to file will be displayed preceded by “->”.  The format is identical to that produced by “ls -dgils”.

このオプションにより、ファイルのinode番号や、権限、ユーザ、オーナー、シンボリックリンクのファイルパス・参照先のファイルも表示できます。

➜   find . -type l -ls
11758148        0 lrwxr-xr-x    1 user      staff                   5  6 14 00:44 ./misc/t/c.txt -> a.txt
11758122        0 lrwxr-xr-x    1 user      staff                   5  6 14 00:43 ./misc/b.txt -> a.txt
...
6791125        0 lrwxr-xr-x    1 user      staff                   8  2 11 00:46 ./c-unix-programming/symlinktest.txt -> test.txt

ファイル構成によっては、いろんなシンボリックリンクが表示されます。

指定したファイルを参照しているシンボリックリンクを表示させる。

上記の find . -type l -ls の結果から、指定したファイルを参照しているものをフィルターする場合、grepを使います。

 find . -type l -ls | grep a.xt
11758148        0 lrwxr-xr-x    1 user      staff                   5  6 14 00:44 ./misc/t/c.txt -> a.txt
11758122        0 lrwxr-xr-x    1 user      staff                   5  6 14 00:43 ./misc/b.txt -> a.txt

これで完了です。