Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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つの要因があります。

  1. Python 3への移行と互換性問題: GDBはPythonスクリプトを介して拡張機能を提供しており、Go言語のデバッグサポートもこの仕組みを利用しています。当時のPythonコミュニティではPython 2からPython 3への移行が進んでおり、両バージョン間には互換性のない変更がいくつか存在しました。特に、print ステートメントが関数になったことや、xrangerange に統合されたこと、文字列フォーマットの変更、例外処理の構文などが挙げられます。このコミットは、runtime-gdb.py がPython 3環境でも動作するように、これらの互換性問題を解消することを目的としています。

  2. 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.Functiongdb.Command: GDBのPython APIを使って、GDB内で直接呼び出せるカスタム関数(例: lencap)やカスタムコマンド(例: info goroutines)を定義できます。

Go言語のランタイムと内部構造

Go言語は独自のランタイムを持っており、ガベージコレクション、スケジューラ、ゴルーチン、チャネル、マップなどの機能を提供します。これらの機能は、C言語で書かれたランタイムコードと、Go言語で書かれた標準ライブラリによって実装されています。GDBでGoプログラムをデバッグする際には、これらのランタイムの内部構造(例えば、ゴルーチンのスタックポインタやプログラムカウンタ、マップのハッシュテーブル構造など)を理解し、GDBがそれらを正しく解釈できるようにする必要があります。

Python 2とPython 3の主な違い

このコミットの背景にあるPythonの互換性問題は、主に以下の点に集約されます。

  • print ステートメント: Python 2では print "Hello" のようにステートメントとして機能しましたが、Python 3では print("Hello") のように関数として呼び出す必要があります。
  • xrangerange: 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では intlong がありましたが、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のランタイム構造への適応に焦点を当てています。

  1. 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の関数呼び出しに統一しています。
  2. xrange から range への移行:

    • Python 3では xrange が存在しないため、if sys.version > '3': xrange = range という条件付きの代入を追加しています。これにより、Python 3環境では xrangerange にエイリアスされ、コードの互換性を保っています。
  3. 文字列フォーマットの更新:

    • '%d''%s' を使用していた箇所が、'{0}'.format(idx)'{0}: {1}'.format(obj.type, dtype) のように str.format() メソッドを使用する形式に変更されています。これはPython 3で推奨されるモダンな文字列フォーマットの方法です。
  4. マップのpretty-printingの修正:

    • MapTypePrinter クラス内の patternre.compile(r'^struct hash<.*>$') から re.compile(r'^map\[.*\].*$') に変更されています。これは、Go 1.2でのマップの型表現が変更されたことに対応するためです。以前は内部的に struct hash<...> のような型名で表現されていたものが、GDBから見た際に map[...] のようなより直接的な型名で認識されるようになったことを示唆しています。
    • マップのバケットをイテレートする際の 2 ** B2 ** (B - 1) の計算で、Bint(B) にキャストされています。これは、GDBから取得した値が gdb.Value オブジェクトであり、そのままでは算術演算ができない場合があるため、明示的に整数に変換していると考えられます。
  5. GDB gdb.Value の整数キャストの互換性対応:

    • GoroutinesCmd クラスの invoke メソッドや GoroutineCmd クラスの invoke メソッド内で、pc (プログラムカウンタ) の値を整数に変換するロジックが追加されています。
      try:
          #python3 / newer versions of gdb
          pc = int(pc)
      except gdb.error:
          pc = int(str(pc), 16)
      
      これは、Python 2では gdb.Value オブジェクト(特に void* 型のポインタ)を直接 int() にキャストしようとすると gdb.error が発生する可能性があるためです。このコードは、まず直接 int() へのキャストを試み、失敗した場合は str(pc) で16進数文字列に変換し、それを int(..., 16) で整数にパースするというフォールバックロジックを提供しています。これにより、Python 2とPython 3の両方で pc の値が正しく整数として扱われるようになります。
  6. 例外処理の構文更新:

    • except: のような汎用的な例外キャッチが except Exception:except gdb.error: のように具体的な例外型を指定する形式に変更されています。これはPython 3の推奨される例外処理の構文であり、より堅牢なエラーハンドリングを可能にします。
  7. 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
 
 #

コアとなるコードの解説

上記の差分から、主要な変更点を以下に解説します。

  1. 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環境では rangexrange の代わりに使用するように設定しています。
  2. スライスとチャネルのpretty-printingの文字列フォーマット変更:

    • SliceTypePrinterChanTypePrinter において、'[%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() 関数に渡す前に明示的に整数にキャストする必要があるためです。
  3. マップのpretty-printingのロジック修正:

    • MapTypePrinterpatternre.compile(r'^struct hash<.*>$') から re.compile(r'^map\[.*\].*$') に変更されています。これはGo 1.2でのマップの型表現の変更に対応するためです。GDBがGoのマップを認識するための正規表現が更新されました。
    • マップのバケット数を計算する 2 ** B2 ** int(B) に変更されています。これも Bgdb.Value オブジェクトであるため、整数にキャストしています。
    • マップのキーと値を表示する際の yield '%d' % cnt, kyield '%d' % (cnt + 1), vyield str(cnt), kyield str(cnt + 1), v に変更されています。これは、Python 3の文字列フォーマットへの対応と、cnt が整数であることを明示的に文字列に変換しているためです。
  4. 例外処理の構文変更:

    • except: のような汎用的な例外キャッチが except Exception:except gdb.error: のように具体的な例外型を指定する形式に変更されています。これにより、どの種類の例外を捕捉しているかが明確になり、コードの可読性と堅牢性が向上します。特に gdb.error はGDBのPython APIが発行する特定のエラーを捕捉するために重要です。
  5. GDB gdb.Value の整数キャストの互換性対応:

    • GoroutinesCmdGoroutineCmdinvoke メソッド内で、pc (プログラムカウンタ) の値を整数に変換する際に、Python 2とPython 3の互換性を考慮したロジックが追加されています。
      try:
          #python3 / newer versions of gdb
          pc = int(pc)
      except gdb.error:
          # Python 2ではvoid*を直接intにキャストできないため、16進数文字列としてパース
          pc = int(str(pc), 16)
      
      この変更により、GDBから取得したポインタ値がPythonのバージョンに関わらず正しく整数として扱われ、gdb.block_for_pc() などの関数に渡せるようになります。
  6. 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値の整数キャストの互換性対応は、デバッグ体験に直接影響を与える重要な改善点です。

関連リンク

参考にした情報源リンク