gdb_bthread_stack.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. #!/usr/bin/env python
  2. # coding=utf-8
  3. # Licensed to the Apache Software Foundation (ASF) under one
  4. # or more contributor license agreements. See the NOTICE file
  5. # distributed with this work for additional information
  6. # regarding copyright ownership. The ASF licenses this file
  7. # to you under the Apache License, Version 2.0 (the
  8. # "License"); you may not use this file except in compliance
  9. # with the License. You may obtain a copy of the License at
  10. #
  11. # http://www.apache.org/licenses/LICENSE-2.0
  12. #
  13. # Unless required by applicable law or agreed to in writing,
  14. # software distributed under the License is distributed on an
  15. # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  16. # KIND, either express or implied. See the License for the
  17. # specific language governing permissions and limitations
  18. # under the License.
  19. """
  20. Bthread Stack Print Tool
  21. this only for running process, core dump is not supported.
  22. Get Started:
  23. 1. gdb attach <pid>
  24. 2. source gdb_bthread_stack.py
  25. 3. bthread_begin
  26. 4. bthread_list
  27. 5. bthread_frame 0
  28. 6. bt / up / down
  29. 7. bthread_end
  30. Commands:
  31. 1. bthread_num: print all bthread nums
  32. 2. bthread_begin <num>: enter bthread debug mode, `num` is max scanned bthreads, default will scan all
  33. 3. bthread_list: list all bthreads
  34. 4. bthread_frame <id>: switch stack to bthread, id will displayed in bthread_list
  35. 5. bthread_meta <id>: print bthread meta
  36. 6. bthread_reg_restore: bthread_frame will modify registers, reg_restore will restore them
  37. 7. bthread_end: exit bthread debug mode
  38. 8. bthread_regs <id>: print bthread registers
  39. when call bthread_frame, registers will be modified,
  40. remember to call bthread_end after debug, or process will be corrupted
  41. after call bthread_frame, you can call `bt`/`up`/`down`, or other gdb command
  42. """
  43. import gdb
  44. bthreads = []
  45. status = False
  46. def get_bthread_num():
  47. root_agent = gdb.parse_and_eval("&(((((*bthread::g_task_control)._nbthreads)._combiner)._agents).root_)")
  48. global_res = int(gdb.parse_and_eval("((*bthread::g_task_control)._nbthreads)._combiner._global_result"))
  49. get_agent = "(*(('bvar::detail::AgentCombiner<long, long, bvar::detail::AddTo<long> >::Agent' *){}))"
  50. last_node = root_agent
  51. while True:
  52. agent = gdb.parse_and_eval(get_agent.format(last_node))
  53. if last_node != root_agent:
  54. val = int(agent["element"]["_value"]["_M_i"])
  55. global_res += val
  56. if agent["next_"] == root_agent:
  57. return global_res
  58. last_node = agent["next_"]
  59. def get_all_bthreads(total):
  60. global bthreads
  61. bthreads = []
  62. count = 0
  63. groups = int(gdb.parse_and_eval("'butil::ResourcePool<bthread::TaskMeta>::_ngroup'")["val"])
  64. for group in range(groups):
  65. blocks = int(gdb.parse_and_eval("(*((*((('butil::static_atomic<butil::ResourcePool<bthread::TaskMeta>::BlockGroup*>' *)('butil::ResourcePool<bthread::TaskMeta>::_block_groups')) + {})).val)).nblock._M_i".format(group)))
  66. for block in range(blocks):
  67. items = int(gdb.parse_and_eval("(*(*(('butil::atomic<butil::ResourcePool<bthread::TaskMeta>::Block*>' *)((*((*((('butil::static_atomic<butil::ResourcePool<bthread::TaskMeta>::BlockGroup*>' *)('butil::ResourcePool<bthread::TaskMeta>::_block_groups')) + {})).val)).blocks) + {}))._M_b._M_p).nitem".format(group, block)))
  68. for item in range(items):
  69. task_meta = gdb.parse_and_eval("*(('bthread::TaskMeta' *)((*(*(('butil::atomic<butil::ResourcePool<bthread::TaskMeta>::Block*>' *)((*((*((('butil::static_atomic<butil::ResourcePool<bthread::TaskMeta>::BlockGroup*>' *)('butil::ResourcePool<bthread::TaskMeta>::_block_groups')) + {})).val)).blocks) + {}))._M_b._M_p).items) + {})".format(group, block, item))
  70. version_tid = (int(task_meta["tid"]) >> 32)
  71. version_butex = gdb.parse_and_eval("*(uint32_t *){}".format(task_meta["version_butex"]))
  72. if version_tid == int(version_butex) and int(task_meta["attr"]["stack_type"]) != 0:
  73. bthreads.append(task_meta)
  74. count += 1
  75. if count >= total:
  76. return
  77. class BthreadListCmd(gdb.Command):
  78. """list all bthreads, print format is 'id\ttid\tfunction\thas stack'"""
  79. def __init__(self):
  80. gdb.Command.__init__(self, "bthread_list", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
  81. def invoke(self, arg, tty):
  82. global status
  83. global bthreads
  84. if not status:
  85. print("Not in bthread debug mode")
  86. return
  87. print("id\t\ttid\t\tfunction\t\thas stack\t\t\ttotal:{}".format(len(bthreads)))
  88. for i, t in enumerate(bthreads):
  89. print("#{}\t\t{}\t\t{}\t\t{}".format(i, t["tid"], t["fn"], "no" if str(t["stack"]) == "0x0" else "yes"))
  90. class BthreadNumCmd(gdb.Command):
  91. """list active bthreads num"""
  92. def __init__(self):
  93. gdb.Command.__init__(self, "bthread_num", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
  94. def invoke(self, arg, tty):
  95. res = get_bthread_num()
  96. print(res)
  97. class BthreadFrameCmd(gdb.Command):
  98. """bthread_frame <id>, select bthread frame by id"""
  99. def __init__(self):
  100. gdb.Command.__init__(self, "bthread_frame", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
  101. def invoke(self, arg, tty):
  102. global status
  103. global bthreads
  104. if not status:
  105. print("Not in bthread debug mode")
  106. return
  107. if not arg:
  108. print("bthread_frame <id>, see 'bthread_list'")
  109. return
  110. bthread_id = int(arg)
  111. if bthread_id >= len(bthreads):
  112. print("id {} exceeds max bthread nums {}".format(bthread_id, len(bthreads)))
  113. return
  114. stack = bthreads[bthread_id]["stack"]
  115. if str(stack) == "0x0":
  116. print("this bthread has no stack")
  117. return
  118. context = gdb.parse_and_eval("(*(('bthread::ContextualStack' *){})).context".format(stack))
  119. rip = gdb.parse_and_eval("*(uint64_t*)({}+7*8)".format(context))
  120. rbp = gdb.parse_and_eval("*(uint64_t*)({}+6*8)".format(context))
  121. rsp = gdb.parse_and_eval("{}+8*8".format(context))
  122. gdb.parse_and_eval("$rip = {}".format(rip))
  123. gdb.parse_and_eval("$rsp = {}".format(rsp))
  124. gdb.parse_and_eval("$rbp = {}".format(rbp))
  125. class BthreadRegsCmd(gdb.Command):
  126. """bthread_regs <id>, print bthread registers"""
  127. def __init__(self):
  128. gdb.Command.__init__(self, "bthread_regs", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
  129. def invoke(self, arg, tty):
  130. global status
  131. global bthreads
  132. if not status:
  133. print("Not in bthread debug mode")
  134. return
  135. if not arg:
  136. print("bthread_regs <id>, see 'bthread_list'")
  137. return
  138. bthread_id = int(arg)
  139. if bthread_id >= len(bthreads):
  140. print("id {} exceeds max bthread nums {}".format(bthread_id, len(bthreads)))
  141. return
  142. stack = bthreads[bthread_id]["stack"]
  143. if str(stack) == "0x0":
  144. print("this bthread has no stack")
  145. return
  146. context = gdb.parse_and_eval("(*(('bthread::ContextualStack' *){})).context".format(stack))
  147. rip = int(gdb.parse_and_eval("*(uint64_t*)({}+7*8)".format(context)))
  148. rbp = int(gdb.parse_and_eval("*(uint64_t*)({}+6*8)".format(context)))
  149. rbx = int(gdb.parse_and_eval("*(uint64_t*)({}+5*8)".format(context)))
  150. r15 = int(gdb.parse_and_eval("*(uint64_t*)({}+4*8)".format(context)))
  151. r14 = int(gdb.parse_and_eval("*(uint64_t*)({}+3*8)".format(context)))
  152. r13 = int(gdb.parse_and_eval("*(uint64_t*)({}+2*8)".format(context)))
  153. r12 = int(gdb.parse_and_eval("*(uint64_t*)({}+1*8)".format(context)))
  154. rsp = int(gdb.parse_and_eval("{}+8*8".format(context)))
  155. print("rip: 0x{:x}\nrsp: 0x{:x}\nrbp: 0x{:x}\nrbx: 0x{:x}\nr15: 0x{:x}\nr14: 0x{:x}\nr13: 0x{:x}\nr12: 0x{:x}".format(rip, rsp, rbp, rbx, r15, r14, r13, r12))
  156. class BthreadMetaCmd(gdb.Command):
  157. """bthread_meta <id>, print task meta by id"""
  158. def __init__(self):
  159. gdb.Command.__init__(self, "bthread_meta", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
  160. def invoke(self, arg, tty):
  161. global status
  162. global bthreads
  163. if not status:
  164. print("Not in bthread debug mode")
  165. return
  166. if not arg:
  167. print("bthread_meta <id>, see 'bthread_list'")
  168. return
  169. bthread_id = int(arg)
  170. if bthread_id >= len(bthreads):
  171. print("id {} exceeds max bthread nums {}".format(bthread_id, len(bthreads)))
  172. return
  173. print(bthreads[bthread_id])
  174. class BthreadBeginCmd(gdb.Command):
  175. """enter bthread debug mode"""
  176. def __init__(self):
  177. gdb.Command.__init__(self, "bthread_begin", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
  178. def invoke(self, arg, tty):
  179. global status
  180. if status:
  181. print("Already in bthread debug mode, do not switch thread before exec 'bthread_end' !!!")
  182. return
  183. active_bthreads = get_bthread_num()
  184. scanned_bthreads = active_bthreads
  185. if arg:
  186. num_arg = int(arg)
  187. if num_arg < active_bthreads:
  188. scanned_bthreads = num_arg
  189. else:
  190. print("requested bthreads {} more than actived, will display {} bthreads".format(num_arg, scanned_bthreads))
  191. print("Active bthreads: {}, will display {} bthreads".format(active_bthreads, scanned_bthreads))
  192. get_all_bthreads(scanned_bthreads)
  193. gdb.parse_and_eval("$saved_rip = $rip")
  194. gdb.parse_and_eval("$saved_rsp = $rsp")
  195. gdb.parse_and_eval("$saved_rbp = $rbp")
  196. status = True
  197. print("Enter bthread debug mode, do not switch thread before exec 'bthread_end' !!!")
  198. class BthreadRegRestoreCmd(gdb.Command):
  199. """restore registers"""
  200. def __init__(self):
  201. gdb.Command.__init__(self, "bthread_reg_restore", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
  202. def invoke(self, arg, tty):
  203. global status
  204. if not status:
  205. print("Not in bthread debug mode")
  206. return
  207. gdb.parse_and_eval("$rip = $saved_rip")
  208. gdb.parse_and_eval("$rsp = $saved_rsp")
  209. gdb.parse_and_eval("$rbp = $saved_rbp")
  210. print("OK")
  211. class BthreadEndCmd(gdb.Command):
  212. """exit bthread debug mode"""
  213. def __init__(self):
  214. gdb.Command.__init__(self, "bthread_end", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
  215. def invoke(self, arg, tty):
  216. global status
  217. if not status:
  218. print("Not in bthread debug mode")
  219. return
  220. gdb.parse_and_eval("$rip = $saved_rip")
  221. gdb.parse_and_eval("$rsp = $saved_rsp")
  222. gdb.parse_and_eval("$rbp = $saved_rbp")
  223. status = False
  224. print("Exit bthread debug mode")
  225. BthreadListCmd()
  226. BthreadNumCmd()
  227. BthreadBeginCmd()
  228. BthreadEndCmd()
  229. BthreadFrameCmd()
  230. BthreadMetaCmd()
  231. BthreadRegRestoreCmd()
  232. BthreadRegsCmd()