LLDBでカスタムコマンドを作ってみた

# はじめに
こんにちは。42 Tokyo というパリ発のエンジニア養成機関の[2023年度アドベントカレンダー](https://qiita.com/advent-calendar/2023/42tokyo)昨日に続いて15日目も担当します、在校生の mogawa と申します。引き続き、`LLDB`ネタで投稿させていただきます。

# LLDBカスタムコマンド
そもそもLLDBはすでに、[複数のコマンドを用意](https://lldb.llvm.org/use/map.html)してくれており、通常そちらで十分だと思うのですが、痒いところに微妙に手が届かないことがあり、少し勉強してみました。

## カスタムコマンドを必要とした端緒
[42東京](https://42tokyo.jp/)の本科生になると、多くの課題でシステムコールしか使うのを許されていないため、まず、自分用汎用関数群(`strlen`や`memmove`などなど)を作成する課題が与えられます。
そして、自分で作成したかわいい自作汎用関数群を携えて、難易度が上がっていく次への課題へと対応していくことになります。

### 連結リスト構造体(`t_list`)
その最初の課題で連結リスト関係の一連の関数群も作成する機会があるのですが、構造は以下の`t_list`の構造体となります。
```c:lib.h
typedef struct s_list
{
    struct s_list *next;
    void *content;
} t_list;
```

### void*型の問題
`t_list`構造体において、実際のデータは、メンバ変数の`content`に預けることになりますが、こちらはどんな型でも受け入れられるように`void *`となっています。
何でも受け入れられる忍耐と寛容のある型なのですが、おかげでデバッガーは、`void*型`をどう表示していいのかわからない状態となり怒られます。
```llvm:LLDB
(lldb) print *content_head->next
(s_list) {
  next = 0x0000600001e44040
  prev = 0x0000600001e44000
  content = 0x0000600001044000
}
(lldb) print *content_head->next->content
error: <user expression 4>:1:1: indirection not permitted on operand of type 'void *'
    1 | *content_head->next->content
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
(lldb) print *content_head->next->content->_str
error: <user expression 5>:1:29: member reference base type 'void' is not a structure or union
    1 | *content_head->next->content->_str
      |  ~~~~~~~~~~~~~~~~~~~~~~~~~~~^ ~~~~
(lldb)
```
### 対応策
デバッガーに`void *`型が指している先の型を`cast`して優しく教えてあげれば、デバッガも答えてくれます。
```llvm:LLDB
(lldb) print ((t_content *)(content_head->next->content))->_str
(char *) 0x0000600001c44000 "One"
```
* でも毎回こんなコマンド打ってられない
* しかもリスト構造なら何個も`next->next->next->next->...`となる

のでカスタムコマンドで解決したいと思います。

# カスタムコマンドを実装してみよう
### 前提
#### Python
`LLDB`は`Python`のモジュールを通じて多くの機能をコントロールできるようになっているので、カスタムコマンドの利用には、Pythonがインストールされている必要があります。
#### .lldbinit
設定は、`.lldbinit`ファイルに保存します。LLDBは`~/.lldbinit`ファイルを起動時に読み込みますので、ファイルを作成しておいてください。

#### 今回のサンプルリスト構造
t_listの`void *`の先に、intとchar*型を持つ`t_data`構造体を格納します。

```c:main.c
typedef	struct	s_data
{
	int		_int;
	char	*_str;
}				t_data;

/*
typedef struct s_list
{
    struct s_list *next;
    void *data;
} t_list;
*/
```
なお、テストのために`t_data`を有するリスト構造を作成するコードを[GITHUB](https://github.com/masaruo/lldb_script_for_tlist.git)に上げておいたので、そちらもご参照ください。


## 作成したい機能
リスト構造を順次最後までループしつつ、格納された情報を印字しデバックできるコードを作成します。
## まずはハローワールド実行
`print_tlist.py`を作成してください。その中で、lldbのモジュールのインポートなどを行い、`print_tlist()`関数を作成してみましょう。
```python:print_tlist.py
import lldb

def print_tlist(debugger, command, result, internal_dict):
	print("hello world!")
```
また、`.lldbinit`に`print_tlist.py`を登録しましょう。
```llvm:.lldbinit
command script import ~/your//print_tlist/file/location.py
```
`lldb`を立ち上げて、`command script add -f print_tlist.print_tlist greeting`と打ち込み、続けて、`greeting`と打ってみると、`hello world!`とプリントされていると思います。
```llvm:LLDB
[~]$ lldb
(lldb) command script add -f print_tlist.print_tlist print_tlist
(lldb) print_tlist
hello world!
(lldb) 
```
## `print_tlist`関数の実装
hello world!が成功したので、正しく、`print_tlist.py`はLLDBに読み込まれているようです。まずは、 LLDBで立ち上げ時に毎回`command script add -f print_tlist.print_tlist print_tlist`でコマンドの登録は面倒なので、こちらを`print_tlist.py`に入れましょう。
```diff_python:print_tlist.py
import lldb

def print_tlist(debugger, command, result, internal_dict):
	print("hello world!")

+def __lldb_init_module(debugger, internal_dict):
+    debugger.HandleCommand("command script add -f print_tlist.print_tlist print_tlist")
```
LLDBを`quit`し再度`lldb`で立ち上げ、print_tlistと打ち込んで、`hello world!`が出ると思います。

### LLDB Python APIのクラス
次に、`print_tlist関数`にlldbの重要なクラスにアクセスできるようにしましょう。
```diff_python:print_tlist.py
import lldb

def print_tlist(debugger, command, result, internal_dict):
-	print("hello world!")
+   target = debugger.GetSelectedTarget()
+   process = target.GetProcess()
+   thread = process.GetSelectedThread()
+   frame = thread.GetSelectedFrame()
```
print_tlistの引数に`debugger`とあるように、debuggerのクラスを関数の引数として受け取れるので、そこから、他のクラスに順次アクセスして、それぞれのクラスが持つ情報にアクセスできるようです。コードでは、
`debugger`->`target`->`process`->`thread`->`frame`をそれぞれの`GetSelected*()`で格納しています。
1. `target` はデバッグ対象やデバッグ情報などを担当
1. `process`は、メモリーアクセスや複数のスレッドを担当
1. `thread`は、スタックフレーム群や実行ステップなどを担当
1. `frame`は、当該スタックフレームを担当し、ローカル変数などを担当
:::note warn
上記は著者の拙い理解ですので、実態と乖離している可能性がかなーりあります！大きな間違えがありましたら、教えていただけましたら、助かります！また、[LLDB公式サイト](https://lldb.llvm.org/python_api.html)を確認してください。
:::

### void*型のキャスト対応
デバッガーに渡すべきデータは、
1. t_list連結リストの`先頭ノード`(`head_of_tlist`)
1. t_listの`void *content`が指している先の型情報（`t_data`）

です。本来は引数などで渡すのがいいのでしょうが、今回は、コードにベタ打ちとします。

１の先頭ノードは`frame`に情報がありそうなので、その中で、[`FindVariable()`](https://lldb.llvm.org/python_api/lldb.SBFrame.html#lldb.SBFrame.FindVariable)して発見します。
```diff_python:print_tlist.py
+    list_head = frame.FindVariable("head_of_tlist")
```


また、２の型情報は全体の情報を持つ`target`の[`FindFirstType()`](https://lldb.llvm.org/python_api/lldb.SBTarget.html#lldb.SBTarget.FindFirstType)で見つけて、[`GetPointerType()`](https://lldb.llvm.org/python_api/lldb.SBType.html#lldb.SBType.GetPointerType)で型タイプをゲットしたいと思います。

```diff_python:print_tlist.py
+    content_type = target.FindFirstType("t_data").GetPointerType()
```
### 連結リストの最後までループ
連結リストの最後まで進めるループを作成して、リストの最後まで順次データを印字するようにしたいと思います。以下のコードを付け加えてください。この中では`current`の値が`NULL`になるまで、ループを回します。

```python:print_tlist.py
    current = list_head
    while current.GetValueAsUnsigned() != 0:
        data = current.GetChildMemberWithName('content').Cast(content_type)
        int_data = data.GetChildMemberWithName('_int').GetValueAsSigned()
        str_data = data.GetChildMemberWithName('_str').GetValue()
        print (f'[:{int_data}:{str_data}]')
        current = current.GetChildMemberWithName('next')
```
この中で、`data`にはt_listの`content`が指している先のデータは、`t_data`の型だと`Cast(content_type)`で渡してあげます。そして、デレファレンスされた`data`から、子要素の`_int & _str`を`GetChildMemberWithName()`で確保し、int型には`GetValueAsSigned()`で、また、char*型には`GetSummary()`で人間の情報に転換し、順次`print`することにより、t_list型連結リストのすべての情報を吐き出すようにしました。

以下が最終のコードです。

```python:print_tlist.py

import lldb

def print_tlist(debugger, command, result, internal_dict):
    target = debugger.GetSelectedTarget()
    process = target.GetProcess()
    thread = process.GetSelectedThread()
    frame = thread.GetSelectedFrame()
    list_head = frame.FindVariable("head_of_tlist")
    content_type = target.FindFirstType("t_data").GetPointerType()
    current = list_head
    while current.GetValueAsUnsigned() != 0:
        data = current.GetChildMemberWithName('content').Cast(content_type)
        int_data = data.GetChildMemberWithName('_int').GetValueAsSigned()
        str_data = data.GetChildMemberWithName('_str').GetSummary()
        print (f'[:{int_data}:{str_data}]')
        current = current.GetChildMemberWithName('next')



def __lldb_init_module(debugger, internal_dict):
    # command script add -f <filename>.<function_name> <cmd_name>
    debugger.HandleCommand("command script add -f print_tlist.print_tlist print_tlist")
```
## 実行テスト
テストコード[GITHUB](https://github.com/masaruo/lldb_script_for_tlist.git)にて、`-g`フラグ付きで`main.c`をコンパイルか、または、ただ単に`make`してください。そして、生成されて実行ファイルを`lldb`で読み込んでください。`b main`でブレークポイントを設定し、`n`でステップ実行を実行し、`head_of_tlist`のデータが作られたところで、作成したカスタムコマンド`print_tlist`を実行してみてください。

```llvm:LLDB
$ lldb print_tlist
(lldb) target create "print_tlist"
Current executable set to '/Users/masaru/SynologyDrive/document/code/lldb/script_for_tlist/print_tlist' (arm64).
(lldb) b main
Breakpoint 1: where = print_tlist`main + 24 at main.c:50:18, address = 0x0000000100003c28
(lldb) run
Process 27664 launched: '/Users/masaru/SynologyDrive/document/code/lldb/script_for_tlist/print_tlist' (arm64)
Process 27664 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100003c28 print_tlist`main at main.c:50:18
   47   {
   48           t_list  *head_of_tlist;
   49  
-> 50           head_of_tlist = get_data();
   51           (void)head_of_tlist;// surpress a compiler's warning
   52           return (0);
   53   }
(lldb) next
Process 27664 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step over
    frame #0: 0x0000000100003c38 print_tlist`main at main.c:52:2
   49  
   50           head_of_tlist = get_data();
   51           (void)head_of_tlist;// surpress a compiler's warning
-> 52           return (0);
   53   }
(lldb) print_tlist
[:0:None]
[:-100:"Hello World"]
[:0:"from 42 Tokyo!"]
[:42:"This programe will create memory leak!"]
```
`t_data`に格納した情報が順次作成されているので、一旦完成です。

# 引数を受け取り汎用性を高めた`ptlist`の実装例
[４２](https://42tokyo.jp/)生向けになりますが、引数を受取り汎用性を高めたものを、こちらの[GITHUB](https://github.com/masaruo/debug_42_tlist.git)に格納していますので、見てみてください。

# 参考文献
[LLDB公式サイト](https://lldb.llvm.org/)
Kodeco Team & Walter Tyree. *[Advanced Apple Debugging & Reverse Enginnering](https://www.kodeco.com/books/advanced-apple-debugging-reverse-engineering)*. Kodeco, 2023年５月