[インデックス 18608] ファイルの概要
このコミットは、Go言語のランタイムデバッグをGDB (GNU Debugger) でサポートするためのPythonスクリプトである src/pkg/runtime/runtime-gdb.py
の変更に関するものです。このファイルは、GDBがGoプログラムの内部構造(文字列、スライス、マップ、チャネル、インターフェース、ゴルーチンなど)を適切に表示(pretty-print)できるようにするためのカスタムコマンドや型プリンタを提供します。
コミット
commit f12a167ba298c1b259dc9dce82676a649d59deb0
Author: Shane Hansen <shanemhansen@gmail.com>
Date: Mon Feb 24 10:13:27 2014 -0500
gdb: Add partial python3 + go1.2 support to runtime-gdb.py
Update #6963 Fixes pretty printing maps and updates
functions for interacting with $len(). goroutine $n bt
remains not working. Tested on gdb using python 2 and 3.
Fixes #7052
Update #6963
Fixes #6698
LGTM=rsc
R=golang-codereviews, josharian, rsc
CC=golang-codereviews
https://golang.org/cl/53590043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f12a167ba298c1b259dc9dce82676a649d59deb0
元コミット内容
このコミットは、runtime-gdb.py
スクリプトにPython 3およびGo 1.2の部分的なサポートを追加することを目的としています。具体的には、マップのpretty-printingの修正、$len()
関数との連携関数の更新が含まれます。ゴルーチンのバックトレース機能(goroutine $n bt
)はまだ動作しないことが明記されています。この変更はPython 2とPython 3の両方でGDB上でテストされています。関連するIssueとして #6963、#7052、#6698 が挙げられています。
変更の背景
この変更の背景には、主に以下の2つの要因があります。
-
Python 3への移行と互換性問題: GDBはPythonスクリプトを介して拡張機能を提供しており、Go言語のデバッグサポートもこの仕組みを利用しています。当時のPythonコミュニティではPython 2からPython 3への移行が進んでおり、両バージョン間には互換性のない変更がいくつか存在しました。特に、
print
ステートメントが関数になったことや、xrange
がrange
に統合されたこと、文字列フォーマットの変更、例外処理の構文などが挙げられます。このコミットは、runtime-gdb.py
がPython 3環境でも動作するように、これらの互換性問題を解消することを目的としています。 -
Go 1.2の変更への対応: Go言語自体も継続的に進化しており、ランタイムの内部構造や型表現がバージョンアップによって変更されることがあります。Go 1.2での変更が、特にマップの内部表現に影響を与え、既存のGDB pretty-printerが正しく機能しなくなった可能性があります。このコミットは、Go 1.2のランタイム構造に合わせて、マップのpretty-printingロジックを更新する必要がありました。
これらの問題により、GoプログラムをGDBでデバッグする際に、特にマップのような複雑なデータ構造が正しく表示されない、あるいはスクリプト自体がエラーで動作しないといった問題が発生していました。このコミットは、これらのデバッグ体験を改善するための重要なステップでした。
前提知識の解説
GDB (GNU Debugger)
GDBは、GNUプロジェクトによって開発された強力なコマンドラインデバッガです。C、C++、Go、Fortranなど、多くのプログラミング言語をサポートしています。GDBは、プログラムの実行を一時停止し、変数の値を検査し、メモリの内容を調べ、レジスタの状態を確認するなど、低レベルでのデバッグを可能にします。
GDBのPythonスクリプティング
GDBはPythonスクリプトを埋め込み、デバッガの機能を拡張することができます。これにより、ユーザーはカスタムコマンド、型プリンタ(pretty-printers)、イベントハンドラなどをPythonで記述し、GDBのデバッグ体験を向上させることができます。Go言語のデバッグサポートも、このPythonスクリプティング機能に大きく依存しています。
- Pretty Printers: GDBは通常、プログラムの変数をその低レベルなメモリ表現で表示します。しかし、Goのスライスやマップのような複雑なデータ構造は、そのままでは人間が理解しにくい形式で表示されます。Pretty printersは、これらのデータ構造をより読みやすい、Go言語のセマンティクスに沿った形式で表示するために使用されます。例えば、Goのスライスを
[elem1, elem2, elem3]
のように表示したり、マップをkey: value
のペアのリストとして表示したりします。 gdb.Value
オブジェクト: GDBのPython APIでは、デバッグ対象のプログラム内の変数やメモリ上の値はgdb.Value
オブジェクトとして表現されます。これらのオブジェクトは、その型情報、値、およびポインタのデリファレンスなどの操作を可能にします。gdb.Function
とgdb.Command
: GDBのPython APIを使って、GDB内で直接呼び出せるカスタム関数(例:len
やcap
)やカスタムコマンド(例:info goroutines
)を定義できます。
Go言語のランタイムと内部構造
Go言語は独自のランタイムを持っており、ガベージコレクション、スケジューラ、ゴルーチン、チャネル、マップなどの機能を提供します。これらの機能は、C言語で書かれたランタイムコードと、Go言語で書かれた標準ライブラリによって実装されています。GDBでGoプログラムをデバッグする際には、これらのランタイムの内部構造(例えば、ゴルーチンのスタックポインタやプログラムカウンタ、マップのハッシュテーブル構造など)を理解し、GDBがそれらを正しく解釈できるようにする必要があります。
Python 2とPython 3の主な違い
このコミットの背景にあるPythonの互換性問題は、主に以下の点に集約されます。
print
ステートメント: Python 2ではprint "Hello"
のようにステートメントとして機能しましたが、Python 3ではprint("Hello")
のように関数として呼び出す必要があります。xrange
とrange
: Python 2のxrange()
はイテレータを返しましたが、Python 3ではrange()
がその役割を担い、xrange()
は削除されました。- 文字列フォーマット: Python 2では
%
演算子による文字列フォーマットが一般的でしたが、Python 3ではstr.format()
メソッドが推奨され、より柔軟なフォーマットが可能になりました。 - 例外処理: Python 2では
except Exception, e:
のように例外をキャッチしましたが、Python 3ではexcept Exception as e:
のようにas
キーワードを使用します。 - 整数型: Python 2では
int
とlong
がありましたが、Python 3ではすべての整数がint
型に統合され、任意精度をサポートします。GDBのgdb.Value
を整数にキャストする際に、Python 2とPython 3で挙動が異なる場合があります。特に、void*
型のポインタを直接int()
にキャストしようとすると、Python 2ではエラーになることがあり、文字列に変換してからint(str(pc), 16)
のように16進数としてパースする必要がありました。
技術的詳細
このコミットで行われた技術的な変更は、主にPython 2とPython 3の互換性、およびGo 1.2のランタイム構造への適応に焦点を当てています。
-
Python 3
print
関数の導入:from __future__ import print_function
をファイルの先頭に追加することで、Python 2環境でもPython 3のprint()
関数構文を使用できるようにしています。これにより、print >>sys.stderr, "..."
のようなPython 2のprint
ステートメントをprint("...", file=sys.stderr)
のようなPython 3の関数呼び出しに統一しています。
-
xrange
からrange
への移行:- Python 3では
xrange
が存在しないため、if sys.version > '3': xrange = range
という条件付きの代入を追加しています。これにより、Python 3環境ではxrange
がrange
にエイリアスされ、コードの互換性を保っています。
- Python 3では
-
文字列フォーマットの更新:
'%d'
や'%s'
を使用していた箇所が、'{0}'.format(idx)
や'{0}: {1}'.format(obj.type, dtype)
のようにstr.format()
メソッドを使用する形式に変更されています。これはPython 3で推奨されるモダンな文字列フォーマットの方法です。
-
マップのpretty-printingの修正:
MapTypePrinter
クラス内のpattern
がre.compile(r'^struct hash<.*>$')
からre.compile(r'^map\[.*\].*$')
に変更されています。これは、Go 1.2でのマップの型表現が変更されたことに対応するためです。以前は内部的にstruct hash<...>
のような型名で表現されていたものが、GDBから見た際にmap[...]
のようなより直接的な型名で認識されるようになったことを示唆しています。- マップのバケットをイテレートする際の
2 ** B
や2 ** (B - 1)
の計算で、B
がint(B)
にキャストされています。これは、GDBから取得した値がgdb.Value
オブジェクトであり、そのままでは算術演算ができない場合があるため、明示的に整数に変換していると考えられます。
-
GDB
gdb.Value
の整数キャストの互換性対応:GoroutinesCmd
クラスのinvoke
メソッドやGoroutineCmd
クラスのinvoke
メソッド内で、pc
(プログラムカウンタ) の値を整数に変換するロジックが追加されています。
これは、Python 2ではtry: #python3 / newer versions of gdb pc = int(pc) except gdb.error: pc = int(str(pc), 16)
gdb.Value
オブジェクト(特にvoid*
型のポインタ)を直接int()
にキャストしようとするとgdb.error
が発生する可能性があるためです。このコードは、まず直接int()
へのキャストを試み、失敗した場合はstr(pc)
で16進数文字列に変換し、それをint(..., 16)
で整数にパースするというフォールバックロジックを提供しています。これにより、Python 2とPython 3の両方でpc
の値が正しく整数として扱われるようになります。
-
例外処理の構文更新:
except:
のような汎用的な例外キャッチがexcept Exception:
やexcept gdb.error:
のように具体的な例外型を指定する形式に変更されています。これはPython 3の推奨される例外処理の構文であり、より堅牢なエラーハンドリングを可能にします。
-
super()
呼び出しの修正:GoLenFunc
,GoCapFunc
,DTypeFunc
,GoroutinesCmd
,GoroutineCmd
,GoIfaceCmd
クラスのコンストラクタで、super(ClassName, self).__init__(...)
の代わりにgdb.Function.__init__(self, ...)
やgdb.Command.__init__(self, ...)
が直接呼び出されています。これは、Python 2とPython 3でのsuper()
の挙動の違い、または特定のGDB Python APIのバージョンにおける推奨される初期化方法に対応するためと考えられます。
コアとなるコードの変更箇所
--- a/src/pkg/runtime/runtime-gdb.py
+++ b/src/pkg/runtime/runtime-gdb.py
@@ -15,10 +15,14 @@ path to this file based on the path to the runtime package.
# circumventing the pretty print triggering.
-import sys, re
-
-print >>sys.stderr, "Loading Go Runtime support."
-
+from __future__ import print_function
+import re
+import sys
+
+print("Loading Go Runtime support.", file=sys.stderr)
+#http://python3porting.com/differences.html
+if sys.version > '3':
+ xrange = range
# allow to manually reload while developing
goobjfile = gdb.current_objfile() or gdb.objfiles()[0]
goobjfile.pretty_printers = []
@@ -61,8 +66,8 @@ class SliceTypePrinter:
if self.val["len"] > self.val["cap"]:
return
ptr = self.val["array"]
- for idx in range(self.val["len"]):
- yield ('[%d]' % idx, (ptr + idx).dereference())
+ for idx in range(int(self.val["len"])):
+ yield ('[{0}]'.format(idx), (ptr + idx).dereference())
class MapTypePrinter:
@@ -72,7 +77,7 @@ class MapTypePrinter:
to inspect their contents with this pretty printer.
"""
- pattern = re.compile(r'^struct hash<.*>$')
+ pattern = re.compile(r'^map\[.*\].*$')
def __init__(self, val):
self.val = val
@@ -90,14 +95,15 @@ class MapTypePrinter:
flags = self.val['flags']
inttype = self.val['hash0'].type
cnt = 0
- for bucket in xrange(2 ** B):
+ for bucket in xrange(2 ** int(B)):
bp = buckets + bucket
if oldbuckets:
oldbucket = bucket & (2 ** (B - 1) - 1)
oldbp = oldbuckets + oldbucket
- oldb = oldbp.dereference()
- if (oldb['overflow'].cast(inttype) & 1) == 0: # old bucket not evacuated yet
- if bucket >= 2 ** (B - 1): continue # already did old bucket
+ oldb = oldbp.dereference()
+ if (oldb['overflow'].cast(inttype) & 1) == 0: # old bucket not evacuated yet
+ if bucket >= 2 ** (B - 1):
+ continue # already did old bucket
bp = oldbp
while bp:
b = bp.dereference()
@@ -109,11 +115,12 @@ class MapTypePrinter:
k = k.dereference()
if flags & 2:
v = v.dereference()
- yield '%d' % cnt, k
- yield '%d' % (cnt + 1), v
+ yield str(cnt), k
+ yield str(cnt + 1), v
cnt += 2
bp = b['overflow']
+
class ChanTypePrinter:
"""Pretty print chan[T] types.
@@ -138,7 +145,7 @@ class ChanTypePrinter:
ptr = (self.val.address + 1).cast(et.pointer())
for i in range(self.val["qcount"]):
j = (self.val["recvx"] + i) % self.val["dataqsiz"]
- yield ('[%d]' % i, (ptr + j).dereference())
+ yield ('[{0}]'.format(i), (ptr + j).dereference())
#
@@ -150,11 +157,11 @@ def makematcher(klass):
try:
if klass.pattern.match(str(val.type)):
return klass(val)
- except:
+ except Exception:
pass
return matcher
-goobjfile.pretty_printers.extend([makematcher(k) for k in vars().values() if hasattr(k, 'pattern')])
+goobjfile.pretty_printers.extend([makematcher(var) for var in vars().values() if hasattr(var, 'pattern')])
#
# For reference, this is what we're trying to do:
@@ -169,34 +176,35 @@ goobjfile.pretty_printers.extend([makematcher(k) for k in vars().values() if has
def is_iface(val):
try:
- return str(val['tab'].type) == "struct runtime.itab *" \
- and str(val['data'].type) == "void *"
- except:
+ return str(val['tab'].type) == "struct runtime.itab *" and str(val['data'].type) == "void *"
+ except gdb.error:
pass
+
def is_eface(val):
try:
- return str(val['_type'].type) == "struct runtime._type *" \
- and str(val['data'].type) == "void *"
- except:
+ return str(val['_type'].type) == "struct runtime._type *" and str(val['data'].type) == "void *"
+ except gdb.error:
pass
+
def lookup_type(name):
try:
return gdb.lookup_type(name)
- except:
+ except gdb.error:
pass
try:
return gdb.lookup_type('struct ' + name)
- except:
+ except gdb.error:
pass
try:
return gdb.lookup_type('struct ' + name[1:]).pointer()
- except:
+ except gdb.error:
pass
_rctp_type = gdb.lookup_type("struct runtime.rtype").pointer()
+
def iface_commontype(obj):
if is_iface(obj):
go_type_ptr = obj['tab']['_type']
@@ -204,9 +212,9 @@ def iface_commontype(obj):
go_type_ptr = obj['_type']
else:
return
-
+
return go_type_ptr.cast(_rctp_type).dereference()
-
+
def iface_dtype(obj):
"Decode type of the data field of an eface or iface struct."
@@ -221,7 +229,7 @@ def iface_dtype(obj):
dynamic_gdb_type = lookup_type(dtype_name)
if dynamic_gdb_type is None:
return
-
+
type_size = int(dynamic_go_type['size'])
uintptr_size = int(dynamic_go_type['size'].type.sizeof) # size is itself an uintptr
if type_size > uintptr_size:
@@ -229,6 +237,7 @@ def iface_dtype(obj):
return dynamic_gdb_type
+
def iface_dtype_name(obj):
"Decode type name of the data field of an eface or iface struct."
@@ -254,15 +263,15 @@ class IfacePrinter:
try:
dtype = iface_dtype(self.val)
- except:
+ except Exception:
return "<bad dynamic type>"
if dtype is None: # trouble looking up, print something reasonable
- return "(%s)%s" % (iface_dtype_name(self.val), self.val['data'])
+ return "({0}){0}".format(iface_dtype_name(self.val), self.val['data'])
try:
return self.val['data'].cast(dtype).dereference()
- except:
+ except Exception:
pass
return self.val['data'].cast(dtype)
@@ -277,16 +286,14 @@ goobjfile.pretty_printers.append(ifacematcher)
# Convenience Functions
#
+\
class GoLenFunc(gdb.Function):
"Length of strings, slices, maps or channels"
-\thow = ((StringTypePrinter, 'len'),
-\t (SliceTypePrinter, 'len'),
-\t (MapTypePrinter, 'count'),
-\t (ChanTypePrinter, 'qcount'))
+\thow = ((StringTypePrinter, 'len'), (SliceTypePrinter, 'len'), (MapTypePrinter, 'count'), (ChanTypePrinter, 'qcount'))
def __init__(self):
-\t\tsuper(GoLenFunc, self).__init__("len")
+\t\tgdb.Function.__init__(self, "len")
def invoke(self, obj):
typename = str(obj.type)
@@ -294,14 +301,14 @@ class GoLenFunc(gdb.Function):
if klass.pattern.match(typename):
return obj[fld]
+\
class GoCapFunc(gdb.Function):
"Capacity of slices or channels"
-\thow = ((SliceTypePrinter, 'cap'),
-\t (ChanTypePrinter, 'dataqsiz'))
+\thow = ((SliceTypePrinter, 'cap'), (ChanTypePrinter, 'dataqsiz'))
def __init__(self):
-\t\tsuper(GoCapFunc, self).__init__("cap")
+\t\tgdb.Function.__init__(self, "cap")
def invoke(self, obj):
typename = str(obj.type)
@@ -309,6 +316,7 @@ class GoCapFunc(gdb.Function):
if klass.pattern.match(typename):
return obj[fld]
+\
class DTypeFunc(gdb.Function):
"""Cast Interface values to their dynamic type.
@@ -316,12 +324,12 @@ class DTypeFunc(gdb.Function):
"""
def __init__(self):
-\t\tsuper(DTypeFunc, self).__init__("dtype")
+\t\tgdb.Function.__init__(self, "dtype")
def invoke(self, obj):
try:
return obj['data'].cast(iface_dtype(obj))
- except:
+ except gdb.error:
pass
return obj
@@ -329,29 +337,47 @@ class DTypeFunc(gdb.Function):
sts = ('idle', 'runnable', 'running', 'syscall', 'waiting', 'moribund', 'dead', 'recovery')
+\
def linked_list(ptr, linkfield):\
while ptr:\
\tyield ptr\
\tptr = ptr[linkfield]
class GoroutinesCmd(gdb.Command):\
"List all goroutines."
def __init__(self):\
-\t\tsuper(GoroutinesCmd, self).__init__("info goroutines", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
+\t\tgdb.Command.__init__(self, "info goroutines", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
-\tdef invoke(self, arg, from_tty):\
+\tdef invoke(self, _arg, _from_tty):\
# args = gdb.string_to_argv(arg)
vp = gdb.lookup_type('void').pointer()
for ptr in linked_list(gdb.parse_and_eval("'runtime.allg'"), 'alllink'):
-\t\t\tif ptr['status'] == 6:\t# 'gdead'
+\t\t\tif ptr['status'] == 6: # 'gdead'
continue
s = ' '
if ptr['m']:
s = '*'
pc = ptr['sched']['pc'].cast(vp)
-\t\t\tsp = ptr['sched']['sp'].cast(vp)
-\t\t\tblk = gdb.block_for_pc(long((pc)))
-\t\t\tprint s, ptr['goid'], "%8s" % sts[long((ptr['status']))], blk.function
+\t\t\t# python2 will not cast pc (type void*) to an int cleanly
+\t\t\t# instead python2 and python3 work with the hex string representation
+\t\t\t# of the void pointer which we can parse back into an int.
+\t\t\t# int(pc) will not work.
+\t\t\ttry:
+\t\t\t\t#python3 / newer versions of gdb
+\t\t\t\tpc = int(pc)
+\t\t\texcept gdb.error:
+\t\t\t\tpc = int(str(pc), 16)
+\t\t\tblk = gdb.block_for_pc(pc)
+\t\t\tprint(s, ptr['goid'], "{0:8s}".format(sts[int(ptr['status'])]), blk.function)
+\
+def find_goroutine(goid):
+\t"""
+\tfind_goroutine attempts to find the goroutine identified by goid.
+\tIt returns a touple of gdv.Value's representing the the stack pointer
+\tand program counter pointer for the goroutine.
+
+\t@param int goid
+
+\t@return tuple (gdb.Value, gdb.Value)
+\t"""
vp = gdb.lookup_type('void').pointer()
for ptr in linked_list(gdb.parse_and_eval("'runtime.allg'"), 'alllink'):
-\t\t\tif ptr['status'] == 6:\t# 'gdead'
+\t\t\tif ptr['status'] == 6: # 'gdead'
continue
if ptr['goid'] == goid:
-\t\t\t\treturn [ptr['sched'][x].cast(vp) for x in 'pc', 'sp']
+\t\t\t\treturn (ptr['sched'][x].cast(vp) for x in ('pc', 'sp'))
return None, None
@@ -380,20 +407,25 @@ class GoroutineCmd(gdb.Command):
"""
def __init__(self):\
-\t\tsuper(GoroutineCmd, self).__init__("goroutine", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
+\t\tgdb.Command.__init__(self, "goroutine", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
-\tdef invoke(self, arg, from_tty):\
+\tdef invoke(self, arg, _from_tty):\
goid, cmd = arg.split(None, 1)
goid = gdb.parse_and_eval(goid)
pc, sp = find_goroutine(int(goid))
if not pc:
-\t\t\tprint "No such goroutine: ", goid
+\t\t\tprint("No such goroutine: ", goid)
return
+\t\ttry:
+\t\t\t#python3 / newer versions of gdb
+\t\t\tpc = int(pc)
+\t\texcept gdb.error:
+\t\t\tpc = int(str(pc), 16)
save_frame = gdb.selected_frame()
gdb.parse_and_eval('$save_pc = $pc')
gdb.parse_and_eval('$save_sp = $sp')
-\t\tgdb.parse_and_eval('$pc = 0x%x' % long(pc))\
-\t\tgdb.parse_and_eval('$sp = 0x%x' % long(sp))
+\t\tgdb.parse_and_eval('$pc = {0}'.format(str(pc)))\
+\t\tgdb.parse_and_eval('$sp = {0}'.format(str(sp)))
try:
gdb.execute(cmd)
finally:
@@ -406,31 +438,33 @@ class GoIfaceCmd(gdb.Command):
"Print Static and dynamic interface types"
def __init__(self):\
-\t\tsuper(GoIfaceCmd, self).__init__("iface", gdb.COMMAND_DATA, gdb.COMPLETE_SYMBOL)
+\t\tgdb.Command.__init__(self, "iface", gdb.COMMAND_DATA, gdb.COMPLETE_SYMBOL)
-\tdef invoke(self, arg, from_tty):\
+\tdef invoke(self, arg, _from_tty):\
for obj in gdb.string_to_argv(arg):\
try:\
#TODO fix quoting for qualified variable names
-\t\t\t\tobj = gdb.parse_and_eval("%s" % obj)
-\t\t\texcept Exception, e:\
-\t\t\t\tprint "Can't parse ", obj, ": ", e
+\t\t\t\tobj = gdb.parse_and_eval(str(obj))
+\t\t\texcept Exception as e:\
+\t\t\t\tprint("Can't parse ", obj, ": ", e)
continue
if obj['data'] == 0:\
dtype = "nil"
else:\
dtype = iface_dtype(obj)
-\t\t\t\t
+\t\t\t\t
if dtype is None:\
-\t\t\t\tprint "Not an interface: ", obj.type
+\t\t\t\tprint("Not an interface: ", obj.type)
continue
-\t\t\tprint "%s: %s" % (obj.type, dtype)
+\t\t\tprint("{0}: {1}".format(obj.type, dtype))
# TODO: print interface's methods and dynamic type's func pointers thereof.
-#rsc: "to find the number of entries in the itab's Fn field look at itab.inter->numMethods
-#i am sure i have the names wrong but look at the interface type and its method count"
+#rsc: "to find the number of entries in the itab's Fn field look at
+# itab.inter->numMethods
+# i am sure i have the names wrong but look at the interface type
+# and its method count"
# so Itype will start with a commontype which has kind = interface
#
コアとなるコードの解説
上記の差分から、主要な変更点を以下に解説します。
-
Python 3互換性のための初期設定:
from __future__ import print_function
: Python 2環境でprint
を関数として扱えるようにするための重要な行です。これにより、Python 2とPython 3の両方で同じprint()
構文を使用できます。print("Loading Go Runtime support.", file=sys.stderr)
: Python 3のprint
関数構文に合わせた変更です。if sys.version > '3': xrange = range
: Python 3でxrange
が削除されたため、Python 3環境ではrange
をxrange
の代わりに使用するように設定しています。
-
スライスとチャネルのpretty-printingの文字列フォーマット変更:
SliceTypePrinter
とChanTypePrinter
において、'[%d]' % idx
のようなPython 2スタイルの文字列フォーマットが'{0}'.format(idx)
のようなPython 3スタイルのstr.format()
に変更されています。これはよりモダンで推奨される方法です。range(self.val["len"])
がrange(int(self.val["len"]))
に変更されています。これは、self.val["len"]
がgdb.Value
オブジェクトであり、range()
関数に渡す前に明示的に整数にキャストする必要があるためです。
-
マップのpretty-printingのロジック修正:
MapTypePrinter
のpattern
がre.compile(r'^struct hash<.*>$')
からre.compile(r'^map\[.*\].*$')
に変更されています。これはGo 1.2でのマップの型表現の変更に対応するためです。GDBがGoのマップを認識するための正規表現が更新されました。- マップのバケット数を計算する
2 ** B
が2 ** int(B)
に変更されています。これもB
がgdb.Value
オブジェクトであるため、整数にキャストしています。 - マップのキーと値を表示する際の
yield '%d' % cnt, k
やyield '%d' % (cnt + 1), v
がyield str(cnt), k
やyield str(cnt + 1), v
に変更されています。これは、Python 3の文字列フォーマットへの対応と、cnt
が整数であることを明示的に文字列に変換しているためです。
-
例外処理の構文変更:
except:
のような汎用的な例外キャッチがexcept Exception:
やexcept gdb.error:
のように具体的な例外型を指定する形式に変更されています。これにより、どの種類の例外を捕捉しているかが明確になり、コードの可読性と堅牢性が向上します。特にgdb.error
はGDBのPython APIが発行する特定のエラーを捕捉するために重要です。
-
GDB
gdb.Value
の整数キャストの互換性対応:GoroutinesCmd
とGoroutineCmd
のinvoke
メソッド内で、pc
(プログラムカウンタ) の値を整数に変換する際に、Python 2とPython 3の互換性を考慮したロジックが追加されています。
この変更により、GDBから取得したポインタ値がPythonのバージョンに関わらず正しく整数として扱われ、try: #python3 / newer versions of gdb pc = int(pc) except gdb.error: # Python 2ではvoid*を直接intにキャストできないため、16進数文字列としてパース pc = int(str(pc), 16)
gdb.block_for_pc()
などの関数に渡せるようになります。
-
GDBカスタムコマンド/関数の初期化:
GoLenFunc
,GoCapFunc
,DTypeFunc
,GoroutinesCmd
,GoroutineCmd
,GoIfaceCmd
のコンストラクタで、super()
を使用した初期化からgdb.Function.__init__(self, ...)
やgdb.Command.__init__(self, ...)
の直接呼び出しに変更されています。これは、Python 2とPython 3のsuper()
の挙動の違いを回避するため、またはGDB Python APIの特定のバージョンで推奨される初期化方法に合わせたものです。
これらの変更は、runtime-gdb.py
スクリプトがPython 2とPython 3の両方の環境で、Go 1.2のランタイム構造に対応しつつ、より安定して機能するようにするためのものです。特に、マップのpretty-printingの修正と、GDB値の整数キャストの互換性対応は、デバッグ体験に直接影響を与える重要な改善点です。
関連リンク
- Go Issue #6963: https://code.google.com/p/go/issues/detail?id=6963 (おそらくGo 1.2でのマップのpretty-printingに関する問題)
- Go Issue #7052: https://code.google.com/p/go/issues/detail?id=7052 (GDBのPython 3サポートに関する問題)
- Go Issue #6698: https://code.google.com/p/go/issues/detail?id=6698 (GDBの
$len()
関数に関する問題) - Go CL 53590043: https://golang.org/cl/53590043 (このコミットのGerritコードレビューページ)
参考にした情報源リンク
- GDB (GNU Debugger) Python 3 compatibility:
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGpx6femimhzY_TfFnaBcSGa3w9yg8tTbYiU1OCcpFs-VeT5rSI4F2ygGHi_ZISca_XXnPydIjqsM6aVgUcZ20Yjo-qGqVCLfXGfqbO2fYAP3z4yuQdjZVh-6VO7mppMKBhSBizZE60Ehpgx04IhjW799F2gyf1mUPb1ouJpUhLcoPtERRIWN_H
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF9_Y5BEOU5XppzPQ7zltAx5BJDHSuJNVwDc6jHYcnZCw2aGrkmhmAUb9NAJpI_JTW6Oz-ILml2e9GFVcebVa8sMamPA2osiY6JTk_hkRJd0lKMUrlKqWYoY2QJYCggUsI2b1f7vz2rrxgl
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGU8tQliribTj1NxfO5ck8FWq52zTay9icpsm71WqSjeXEyDIF-kmri8V0pBWIBuq1P_Nf_lxGZy0LTHJiQPwd18J3woWm1ye9sIQ3UWYk3xJU6jr7kd2NN7nU-hRfSwlxTqdOVokCocHk9kJXYXiwtCMZ_Iy1_ay-msjji39XraF0GtFTxvmq5dPp8j60=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF0V5nHXYTkK2KAE5zoHbqL8N1wczOyfHUFmLB_cL9wUZGWeeqfMUAjszFhfP9d-pcZhVsjdTkrMAuhXHoxP5N2kaWboOwvQOM0TnRQknyZNwUK94aXH55XrJ1Xn-9Jwg6CJ1j0myXcLppac-0=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEz4em_Wh6UfXMnLfeDGiegeHIIUxbaCZ7IO0RkDszMqBOBK2Tf2O7SjywqQo8ksmPd5_VXmC1bgJ7zYpLa0-dPn5zF8VOL-pgh7OY8ylbF5Psquw2gQin1CnepznmL7YjDBUDB4vSWu-0jRH4VPBkn7VDITQ3D67z1w9tMyhAUbr-54Ok=
- Python 3 Porting Guide: http://python3porting.com/differences.html (コミット内のコメントで参照されている)