Cコンパイラゼミ2024 | 2024-07-07のCコンパイラゼミ >>

switch-caseを利用した変なコード

C言語のswitch-case

デバッグに苦労したバグ

  1. map.cをコンパイルするとセグフォが起きる
  2. map_put2という関数のmap->size++;の行でセグフォが起きる
  3. インクリメントを手で分解するとエラーが起きなくなる
    • ここからインクリメント部分で同じノードを2回挿入している箇所が怪しいとにらむ
  4. gdbでデバッグをして、analyse.cのND_DOTに対するパターンマッチ部分でエラーが起きているとわかる
  5. printデバッグをすると、node->lhsにはND_VARREFが来ているとわかる
  6. ND_VARREFはanalyseの段階で新しく発生するノードであって、analyse前にこのノードがあるはずがない
  7. インクリメント部分でコピーせずにノードを入れているため、1度目にND_IDENTからND_VARREFに変換され、2度目にanalyseをすることでエラーが起こっていると判明する

副作用はside effect

GDB

また別のプログラムでセグフォが起きて、 1時間くらい延々と格闘した結果が

mov rdi, [rax] # iのアドレスにあるiの値を取得している

の部分をrdiからediに書き換えれば済むというものだった。

このアセンブリの中にmov [rax], ediというコードがあって、ここでセグフォが起きていた。なのでraxの値がおかしいんじゃないかというところまでは予想が合ってた。でも、raxの値がおかしいのはスタックの管理を間違えたからではなくて、raxのアドレスの計算が間違っていた。何回も違う場所を疑って、結局一つ一つのアセンブリを読む羽目になった。

  nop
  mov rax, rbp
  sub rax, 8 # bのアドレスを取得している
  push rax
# gen ND_MUL
# gen ND_ASSIGN_SUB
# gen_ref ND_VARREF
  mov rax, rbp
  sub rax, 12 # iのアドレスを取得している
  push rax
# gen ND_NUM
  push 1 # 1 | iのアドレス | bのアドレスの形で格納されてる
# endgen ND_NUM
  pop rdi # 1
  pop rax # iのアドレス
  sub [rax], rdi # i -= 1をしている
  mov edi, [rax] # iのアドレスの値を取得している
  push rdi # i - 1 | bのアドレスの形で格納されてる
# endgen ND_ASSIGN_SUB
# gen ND_NUM
  push 4
# endgen ND_NUM
  pop rdi
  pop rax # i - 1が入っている
  imul rax, rdi # (i - 1) * 4をしている
  push rax
# endgen ND_MUL
  pop rdi # (i - 1) * 4が入っている
  pop rax # bのアドレスが入っている
  add rax, rdi # b + (i - 1) * 4をしている
  push rax
# endgen ND_ADD
# gen ND_NUM
  push 42
# endgen ND_NUM
  pop rdi # 42が入っている
  pop rax # b + (i - 1) * 4のアドレスが入っている
  mov [rax], edi # b + (i - 1) * 4のアドレスに42を代入している
  push rdi