使用GDB分析PostgreSQL

使用GDB分析PostgreSQL

​ 原创 xiongcc [ PostgreSQL学徒 ](javascript:void(0)😉 昨天

How to Analyze a PostgreSQL Crash Dump File

介绍

在这篇博客中,我将讨论如何生成崩溃转储文件(也叫做coredump)和一些通用的GDB命令,以帮助开发人员解决PostgreSQL和其他应用程序中与崩溃相关的问题。对问题如何正确分析通常需要花费一定时间并且对应用程序源代码也需要一定程度的了解,从经验上看,有时看大环境而不是看崩溃点可能会更好。

What is a Crash Dump File?

coredump文件是一个由应用程序崩溃时工作内存的状态记录所组成的文件。这种状态由内存地址和CPU寄存器的堆栈表示,通常情况下,只用内存地址和CPU寄存器进行调试是非常困难的,因为它们不会告诉你有关应用程序的逻辑。参考下面的coredump信息,它显示了崩溃时内存地址的回溯跟踪。

#1  0x00687a3d in ?? ()#2  0x00d37f06 in ?? ()#3  0x00bf0ba4 in ?? ()#4  0x00d3333b in ?? ()#5  0x00d3f682 in ?? ()#6  0x00d3407b in ?? ()#7  0x00d3f2f7 in ?? ()

不是很有用吧?所以,当我们看到一个看起来像这样的coredump文件时,这意味着应用程序构建时没有启用符号调试,使得这个coredump文件没有用。如果是这种情况,你需要安装该应用程序的调试版本,或者在启用调试的情况下重新构建该应用程序。

如何生成coredump文件

在生成coredump文件之前,我们需要确保应用程序是启用了符号调试进行构建的。这可以通过执行这样的./configure脚本来完成。

./configure enable-debug

这会在src/Makefile.globalCFLAGS中添加-g参数,优化级别设置为2(-O2)。我更倾向于将优化级别改为0(-O0),这样当我们使用GDB浏览堆栈时,调试将更有意义,而不是跳来跳去,我们将能够在内存中打印出大多数变量值,而不是在GDB中得到optimized out的错误。

CFLAGS = -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -g -O0

现在,我们可以启用coredump的生成了。这可以通过用户命令来完成。

ulimit -c unlimited

要禁用的话:

ulimit -c 0

请确保有足够的磁盘空间,因为coredump文件通常非常大,因为它记录了从开始到崩溃时所有内存的执行状态,并确保在启动PostgreSQL之前在shell中设置了ulimit。当PostgreSQL崩溃时,一个名为core的coredump文件会在$PGDATA目录下生成

使用GDB调试coredump

GDB(GNU Debugger)是一个可移植的调试器,可以在许多类Unix的系统上运行,可以与许多编程语言一起工作,是我最喜欢的分析coredump文件的工具。为了演示,我故意在PostgreSQL的源代码中添加一行,当运行CREATE TABLE命令时,会导致segmentation fault的崩溃。

假设PostgreSQL已经崩溃,并在这个位置~/highgo/git/postgres/postgresdb/core生成了一个core文件。我首先会使用file命令来了解更多关于core文件的信息。信息包括内核信息,以及生成它的程序。

caryh@HGPC01:~$ file /home/caryh/highgo/git/postgres/postgresdb/corepostgresdb/core: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, from 'postgres: cary cary [local] CREATE TABLE', real uid: 1000, effective uid: 1000, real gid: 1000, effective gid: 1000, execfn: '/home/caryh/highgo/git/postgres/highgo/bin/postgres', platform: 'x86_64'caryh@HGPC01:~$

file命令告诉我core文件是由/home/caryh/highgo/git/postgres/highgo/bin/postgres生成的,因为我会这样使用GDB

gdb /home/caryh/highgo/git/postgres/highgo/bin/postgres -c  /home/caryh/highgo/git/postgres/postgresdb/coreGNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-gitCopyright (C) 2018 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.  Type "show copying"and "show warranty" for details.This GDB was configured as "x86_64-linux-gnu".Type "show configuration" for configuration details.For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/>.Find the GDB manual and other documentation resources online at:<http://www.gnu.org/software/gdb/documentation/>.For help, type "help".Type "apropos word" to search for commands related to "word"...Reading symbols from /home/caryh/highgo/git/postgres/highgo/bin/postgres...done.[New LWP 27417][Thread debugging using libthread_db enabled]Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".Core was generated by `postgres: cary cary [local] CREATE TABLE                                 '.Program terminated with signal SIGSEGV, Segmentation fault.#0  heap_insert (relation=relation@entry=0x7f872f532228, tup=tup@entry=0x55ba8290f778, cid=0, options=options@entry=0,    bistate=bistate@entry=0x0) at heapam.c:18401840            ereport(LOG,(errmsg("heap tuple len = %d", heaptup->t_len)));(gdb)

在core文件上运行gdb后,立即显示崩溃的位置在heapam.c:1840,这正是我故意添加的会导致崩溃的行。

使用GDB命令

用gdb,识别崩溃的位置非常容易,因为在core文件上运行gdb后,它会立即告诉你。不幸的是,95%的时候,崩溃的位置并不是问题的真正原因。这就是为什么我在前面提到,有时看大环境而不是看崩溃点可能会更好。崩溃很可能是由于在应用程序到达崩溃点之前应用程序逻辑中的错误引起的。即使你修复了崩溃,应用逻辑中的错误仍然存在,而且很有可能,应用程序以后会在其他地方崩溃或产生不满意的结果。因此,了解一些强大的GDB命令是值得的,这些命令可以帮助我们更好地理解调用栈,以确定真正的根源。

The bt (Back Trace) command

bt命令显示了从应用程序开始一直到崩溃点的一系列调用堆栈。在启用完全调试的情况下,你将能够看到每个函数的参数和传入的值,以及它们被调用的源文件和行号。这使得开发人员可以向后检查在早期处理中任何潜在的应用逻辑错误。

(gdb) bt#0  heap_insert (relation=relation@entry=0x7f872f532228, tup=tup@entry=0x55ba8290f778, cid=0, options=options@entry=0,    bistate=bistate@entry=0x0) at heapam.c:1840#1  0x000055ba81ccde3e in simple_heap_insert (relation=relation@entry=0x7f872f532228, tup=tup@entry=0x55ba8290f778)    at heapam.c:2356#2  0x000055ba81d7826d in CatalogTupleInsert (heapRel=0x7f872f532228, tup=0x55ba8290f778) at indexing.c:228#3  0x000055ba81d946ea in TypeCreate (newTypeOid=newTypeOid@entry=0, typeName=typeName@entry=0x7ffcf56ef820 "test",    typeNamespace=typeNamespace@entry=2200, relationOid=relationOid@entry=16392, relationKind=relationKind@entry=114 'r',    ownerId=ownerId@entry=16385, internalSize=-1, typeType=99 'c', typeCategory=67 'C', typePreferred=false,    typDelim=44 ',', inputProcedure=2290, outputProcedure=2291, receiveProcedure=2402, sendProcedure=2403,    typmodinProcedure=0, typmodoutProcedure=0, analyzeProcedure=0, elementType=0, isImplicitArray=false, arrayType=16393,    baseType=0, defaultTypeValue=0x0, defaultTypeBin=0x0, passedByValue=false, alignment=100 'd', storage=120 'x',    typeMod=-1, typNDims=0, typeNotNull=false, typeCollation=0) at pg_type.c:484#4  0x000055ba81d710bc in AddNewRelationType (new_array_type=16393, new_row_type=<optimized out>, ownerid=<optimized out>,    new_rel_kind=<optimized out>, new_rel_oid=<optimized out>, typeNamespace=2200, typeName=0x7ffcf56ef820 "test")    at heap.c:1033#5  heap_create_with_catalog (relname=relname@entry=0x7ffcf56ef820 "test", relnamespace=relnamespace@entry=2200,    reltablespace=reltablespace@entry=0, relid=16392, relid@entry=0, reltypeid=reltypeid@entry=0,    reloftypeid=reloftypeid@entry=0, ownerid=16385, accessmtd=2, tupdesc=0x55ba8287c620, cooked_constraints=0x0,    relkind=114 'r', relpersistence=112 'p', shared_relation=false, mapped_relation=false, oncommit=ONCOMMIT_NOOP,    reloptions=0, use_user_acl=true, allow_system_table_mods=false, is_internal=false, relrewrite=0, typaddress=0x0)    at heap.c:1294#6  0x000055ba81e3782a in DefineRelation (stmt=stmt@entry=0x55ba82876658, relkind=relkind@entry=114 'r', ownerId=16385,    ownerId@entry=0, typaddress=typaddress@entry=0x0,    queryString=queryString@entry=0x55ba82855648 "create table test (a int, b char(10)) using heap;") at tablecmds.c:885#7  0x000055ba81fd5b2f in ProcessUtilitySlow (pstate=pstate@entry=0x55ba82876548, pstmt=pstmt@entry=0x55ba828565a0,    queryString=queryString@entry=0x55ba82855648 "create table test (a int, b char(10)) using heap;",    context=context@entry=PROCESS_UTILITY_TOPLEVEL, params=params@entry=0x0, queryEnv=queryEnv@entry=0x0, qc=0x7ffcf56efe50,    dest=0x55ba82856860) at utility.c:1161#8  0x000055ba81fd4120 in standard_ProcessUtility (pstmt=0x55ba828565a0,    queryString=0x55ba82855648 "create table test (a int, b char(10)) using heap;", context=PROCESS_UTILITY_TOPLEVEL,    params=0x0, queryEnv=0x0, dest=0x55ba82856860, qc=0x7ffcf56efe50) at utility.c:1069#9  0x000055ba81fd1962 in PortalRunUtility (portal=0x55ba828b7dd8, pstmt=0x55ba828565a0, isTopLevel=<optimized out>,    setHoldSnapshot=<optimized out>, dest=<optimized out>, qc=0x7ffcf56efe50) at pquery.c:1157#10 0x000055ba81fd23e3 in PortalRunMulti (portal=portal@entry=0x55ba828b7dd8, isTopLevel=isTopLevel@entry=true,    setHoldSnapshot=setHoldSnapshot@entry=false, dest=dest@entry=0x55ba82856860, altdest=altdest@entry=0x55ba82856860,    qc=qc@entry=0x7ffcf56efe50) at pquery.c:1310#11 0x000055ba81fd2f51 in PortalRun (portal=portal@entry=0x55ba828b7dd8, count=count@entry=9223372036854775807,    isTopLevel=isTopLevel@entry=true, run_once=run_once@entry=true, dest=dest@entry=0x55ba82856860,    altdest=altdest@entry=0x55ba82856860, qc=0x7ffcf56efe50) at pquery.c:779#12 0x000055ba81fce967 in exec_simple_query (query_string=0x55ba82855648 "create table test (a int, b char(10)) using heap;")    at postgres.c:1239#13 0x000055ba81fd0d7e in PostgresMain (argc=<optimized out>, argv=argv@entry=0x55ba8287fdb0, dbname=<optimized out>,    username=<optimized out>) at postgres.c:4315#14 0x000055ba81f4f52a in BackendRun (port=0x55ba82877110, port=0x55ba82877110) at postmaster.c:4536#15 BackendStartup (port=0x55ba82877110) at postmaster.c:4220#16 ServerLoop () at postmaster.c:1739#17 0x000055ba81f5063f in PostmasterMain (argc=3, argv=0x55ba8284fee0) at postmaster.c:1412#18 0x000055ba81c91c04 in main (argc=3, argv=0x55ba8284fee0) at main.c:210(gdb)

The f (Fly) command

f命令后面跟着一个堆栈号,允许gdb跳到由bt命令列出的特定调用堆栈,并允许你打印该特定堆栈中的其他变量。比如说

(gdb) f 3#3  0x000055ba81d946ea in TypeCreate (newTypeOid=newTypeOid@entry=0, typeName=typeName@entry=0x7ffcf56ef820 "test",    typeNamespace=typeNamespace@entry=2200, relationOid=relationOid@entry=16392, relationKind=relationKind@entry=114 'r',    ownerId=ownerId@entry=16385, internalSize=-1, typeType=99 'c', typeCategory=67 'C', typePreferred=false,    typDelim=44 ',', inputProcedure=2290, outputProcedure=2291, receiveProcedure=2402, sendProcedure=2403,    typmodinProcedure=0, typmodoutProcedure=0, analyzeProcedure=0, elementType=0, isImplicitArray=false, arrayType=16393,    baseType=0, defaultTypeValue=0x0, defaultTypeBin=0x0, passedByValue=false, alignment=100 'd', storage=120 'x',    typeMod=-1, typNDims=0, typeNotNull=false, typeCollation=0) at pg_type.c:484484                     CatalogTupleInsert(pg_type_desc, tup);(gdb)

这迫使gdb跳到堆栈编号3,即pg_type.c:484处。在这里,你可以检查这个框架中的所有其他变量(在函数TypeCreate中)。

The p (Print) command

gdb中最常用的命令,可以用来打印变量地址和值

(gdb) p tup$1 = (HeapTuple) 0x55ba8290f778(gdb) p pg_type_desc$2 = (Relation) 0x7f872f532228(gdb)  p * tup$3 = {t_len = 176, t_self = {ip_blkid = {bi_hi = 65535, bi_lo = 65535}, ip_posid = 0}, t_tableOid = 0,  t_data = 0x55ba8290f790}(gdb) p * pg_type_desc$4 = {rd_node = {spcNode = 1663, dbNode = 16384, relNode = 1247}, rd_smgr = 0x55ba828e2a38, rd_refcnt = 2, rd_backend = -1,  rd_islocaltemp = false, rd_isnailed = true, rd_isvalid = true, rd_indexvalid = true, rd_statvalid = false,  rd_createSubid = 0, rd_newRelfilenodeSubid = 0, rd_firstRelfilenodeSubid = 0, rd_droppedSubid = 0,  rd_rel = 0x7f872f532438, rd_att = 0x7f872f532548, rd_id = 1247, rd_lockInfo = {lockRelId = {relId = 1247, dbId = 16384}},  rd_rules = 0x0, rd_rulescxt = 0x0, trigdesc = 0x0, rd_rsdesc = 0x0, rd_fkeylist = 0x0, rd_fkeyvalid = false,  rd_partkey = 0x0, rd_partkeycxt = 0x0, rd_partdesc = 0x0, rd_pdcxt = 0x0, rd_partcheck = 0x0, rd_partcheckvalid = false,  rd_partcheckcxt = 0x0, rd_indexlist = 0x7f872f477d00, rd_pkindex = 0, rd_replidindex = 0, rd_statlist = 0x0,  rd_indexattr = 0x0, rd_keyattr = 0x0, rd_pkattr = 0x0, rd_idattr = 0x0, rd_pubactions = 0x0, rd_options = 0x0,  rd_amhandler = 0, rd_tableam = 0x55ba82562c20 <heapam_methods>, rd_index = 0x0, rd_indextuple = 0x0, rd_indexcxt = 0x0,  rd_indam = 0x0, rd_opfamily = 0x0, rd_opcintype = 0x0, rd_support = 0x0, rd_supportinfo = 0x0, rd_indoption = 0x0,  rd_indexprs = 0x0, rd_indpred = 0x0, rd_exclops = 0x0, rd_exclprocs = 0x0, rd_exclstrats = 0x0, rd_indcollation = 0x0,  rd_opcoptions = 0x0, rd_amcache = 0x0, rd_fdwroutine = 0x0, rd_toastoid = 0, pgstat_info = 0x55ba828d5cb0}(gdb)

使用 * ,您可以告诉p命令打印指针的地址或指针所指向的值。

The x (examine) command

x命令用来检查一个具有指定大小和格式的内存块内容。下面的例子试图检查一个HeapTuple结构体中的t_data值。注意,我们首先打印*tup指针以了解t_data的大小为176,然后我们使用x命令检查t_data所指向的前176个字节

(gdb)  p *tup$6 = {t_len = 176, t_self = {ip_blkid = {bi_hi = 65535, bi_lo = 65535}, ip_posid = 0}, t_tableOid = 0,  t_data = 0x55ba8290f790}(gdb)  p tup->t_data$7 = (HeapTupleHeader) 0x55ba8290f790(gdb) x/176bx  tup->t_data0x55ba8290f790: 0xc0    0x02    0x00    0x00    0xff    0xff    0xff    0xff0x55ba8290f798: 0x47    0x00    0x00    0x00    0xff    0xff    0xff    0xff0x55ba8290f7a0: 0x00    0x00    0x1f    0x00    0x01    0x00    0x20    0xff0x55ba8290f7a8: 0xff    0xff    0x0f    0x00    0x00    0x00    0x00    0x000x55ba8290f7b0: 0x0a    0x40    0x00    0x00    0x74    0x65    0x73    0x740x55ba8290f7b8: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x000x55ba8290f7c0: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x000x55ba8290f7c8: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x000x55ba8290f7d0: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x000x55ba8290f7d8: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x000x55ba8290f7e0: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x000x55ba8290f7e8: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x000x55ba8290f7f0: 0x00    0x00    0x00    0x00    0x98    0x08    0x00    0x000x55ba8290f7f8: 0x01    0x40    0x00    0x00    0xff    0xff    0x00    0x630x55ba8290f800: 0x43    0x00    0x01    0x2c    0x08    0x40    0x00    0x000x55ba8290f808: 0x00    0x00    0x00    0x00    0x09    0x40    0x00    0x000x55ba8290f810: 0xf2    0x08    0x00    0x00    0xf3    0x08    0x00    0x000x55ba8290f818: 0x62    0x09    0x00    0x00    0x63    0x09    0x00    0x000x55ba8290f820: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x000x55ba8290f828: 0x00    0x00    0x00    0x00    0x64    0x78    0x00    0x000x55ba8290f830: 0x00    0x00    0x00    0x00    0xff    0xff    0xff    0xff0x55ba8290f838: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00(gdb)

前文译自:https://www.highgo.ca/2020/11/07/how-to-analyze-a-postgresql-crash-dump-file/

除了使用GDB简单分析core文件之外,还要了解一些常用的PostgreSQL编译项。

1.enable-debug:开启调试符号使调试器跟踪代码,建议打开2.enable-cassert:开启断言,打开健全性检查选项,会检查许多不可能的条件和选项,按需打开3.enable-depend:启用自动依赖跟踪。仅支持GCC4.enable-dtrace:启动动态跟踪,对于支持动态跟踪的系统,可以开启

对于CFLAGS,还会经常看到,-ggdb -g3,这个编译项是什么意思呢

1.-g以OS本地格式(stabs,COFF,XCOFF或DWARF 2)产生调试信息。2.-ggdb生成专门用于gdb的调试信息。3.-ggdb3会生成额外的调试信息,例如:包含宏定义。-ggdb本身没有指定级别的默认值4.-ggdb2(即,级别2的gdb)。

除此之外,还经常搭配-g -O0,这个主要是方便我们调试的配置项。假如我们在调试的时候,经常遇到GDB查看变量值的时候输出``,我们就可以将GCC优化选项调整为O1或是O0,GCC在O2O3优化选项下会将代码优化的比较多,调试器有可能会找不到变量的信息,对应的就是看到 var =通常可以将优化级别降低到O0,完全关闭优化,可以保留所有的变量和代码信息。使用O1优化有可能也可以看得到变量的值。当然,这种直接降低优化级别的方法还是比较暴力的。如果你的程序比较大,运行到你需要调试的地方需要很久的情况下,通常都是行不通的,毕竟所有优化选项都关掉了,程序跑的会非常慢。

How to dump out a backtrace during runtime

总览

PostgreSQL是一个伟大的开源数据库,由世界上许多伟大的软件工程师开发和维护。在每个版本中,都有许多功能添加到这个开源数据库中。例如,在PostgreSQL 13中引入的backtrace_functions对开发者很有帮助,它允许开发者在服务器上发生某些错误时转储出backtrace。在这篇博客中,我将对它进行更详细的解释。

What is backtrace_functions?

backtrace_functions选项是为开发者引入的一个选项。你可以指定一个用逗号隔开的c函数名称的列表,如果一个错误发生了并与给定列表中的任何C函数相匹配,那么backtrace将被记录在日志文件中。这对于调试源代码的某些特定区域非常有用,特别是当错误随机发生时。正如文件中提到的,这个选项不是在所有的平台上都可用,而且backtrace的质量取决于编译选项。由于这个原因,本博客中使用的所有例子都是在Ubuntu 18.04上测试的,gcc版本为7.5.0。

How to make it work?

该功能于2019年11月首次提交,如下图所示。

commit 71a8a4f6e36547bb060dbcc961ea9b57420f7190Author: Alvaro Herrera <alvherre@alvh.no-ip.org>Date:   Fri Nov 8 15:44:20 2019 -0300    Add backtrace support for error reporting

要使用这个功能,你需要在postgresql.conf文件中加入关键词backtrace_functions和C函数名称。它可以是单一的C函数名,也可以是用逗号分隔的C函数名列表。在这篇博客中,我们用circle_in作为一个例子。下面是我在postgresql.conf文件中添加的内容。

$ tail -n1 $PGDATA/postgresql.confbacktrace_functions='circle_in'

重启服务器后,使用psql连接到服务器,并输入以下SQL查询(本例中使用的PostgreSQL源代码是基于2020年3月的PostgreSQL13开发分支,如果你想测试这个功能,你可以创建自己的错误)。

postgres=# create temp table src (f1 text);postgres=# INSERT INTO tbl_circle(a) VALUES('( 1 , 1 ) , 5'::circle );ERROR:  invalid input syntax for type circle: "( 1 , 1 ) , 5"LINE 1: INSERT INTO tbl_circle(a) VALUES('( 1 , 1 ) , 5'::circle );

出现了一个错误,现在,如果你转储日志文件,$ cat logfile,你应该看到如下内容。

2020-12-14 13:43:22.541 PST [25220] ERROR:  invalid input syntax for type circle: "( 1 , 1 ) , 5" at character 342020-12-14 13:43:22.541 PST [25220] BACKTRACE:      postgres: david postgres [local] INSERT(circle_in+0x1ca) [0x55bc1cdfaa8a]    postgres: david postgres [local] INSERT(InputFunctionCall+0x7b) [0x55bc1cec375b]    postgres: david postgres [local] INSERT(OidInputFunctionCall+0x48) [0x55bc1cec39c8]    postgres: david postgres [local] INSERT(coerce_type+0x19a) [0x55bc1cb9d72a]    postgres: david postgres [local] INSERT(coerce_to_target_type+0x9d) [0x55bc1cb9e0ed]    postgres: david postgres [local] INSERT(+0x1c748f) [0x55bc1cba248f]    postgres: david postgres [local] INSERT(transformExpr+0x14) [0x55bc1cba59f4]    postgres: david postgres [local] INSERT(transformExpressionList+0x9f) [0x55bc1cbb273f]    postgres: david postgres [local] INSERT(transformStmt+0x1a47) [0x55bc1cb782d7]    postgres: david postgres [local] INSERT(parse_analyze+0x4f) [0x55bc1cb7957f]    postgres: david postgres [local] INSERT(pg_analyze_and_rewrite+0x12) [0x55bc1cd9fd62]    postgres: david postgres [local] INSERT(+0x3c531f) [0x55bc1cda031f]    postgres: david postgres [local] INSERT(PostgresMain+0x1f04) [0x55bc1cda25b4]    postgres: david postgres [local] INSERT(+0x34c168) [0x55bc1cd27168]    postgres: david postgres [local] INSERT(PostmasterMain+0xeff) [0x55bc1cd2827f]    postgres: david postgres [local] INSERT(main+0x4a4) [0x55bc1ca9b4e4]    /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f6c22eeab97]    postgres: david postgres [local] INSERT(_start+0x2a) [0x55bc1ca9b5aa]2020-12-14 13:43:22.541 PST [25220] STATEMENT:  INSERT INTO tbl_circle(a) VALUES('( 1 , 1 ) , 5'::circle );

我们可以看到错误发生在circle_in函数中,该函数被InputFunctionCall函数所调用,如此类推。这和你用gdb调试源代码时的backtrace完全一样,但你也会发现有些函数名显示为十六进制字符串,如0x3c531f。一些函数名没有显示出来的原因是它们是静态函数。对于这些函数,我们需要使用addr2line来将地址转换成文件名和行号。比如说

addr2line 0x3c531f -f -e `which postgres`

其中-f显示函数名称以及文件和行号,-e用来指定应翻译地址的可执行文件的名称。

这取决于编译参数,如果你用默认的CFLAG编译PostgreSQL,你可能会得到下面这样的结果。

$ addr2line 0x3c531f -f -e `which postgres`exec_simple_querypostgres.c:?

其中行号并没有显示出来。为了得到行号和文件名,让我们在CFLAGS中添加选项-ggdb,然后重新编译源代码。

./configure '--prefix=/home/david/pgapp' 'CFLAGS=-ggdb'

现在,如果你重复上面的测试,你会得到一个类似的如下backtrace

2020-12-14 13:56:28.780 PST [3459] ERROR:  invalid input syntax for type circle: "( 1 , 1 ) , 5" at character 342020-12-14 13:56:28.780 PST [3459] BACKTRACE:      postgres: david postgres [local] INSERT(circle_in+0x275) [0x56522b34137f]    postgres: david postgres [local] INSERT(InputFunctionCall+0xe9) [0x56522b457d39]    postgres: david postgres [local] INSERT(OidInputFunctionCall+0x4b) [0x56522b45805f]    postgres: david postgres [local] INSERT(stringTypeDatum+0x5e) [0x56522afe3220]    postgres: david postgres [local] INSERT(coerce_type+0x312) [0x56522afc055b]    postgres: david postgres [local] INSERT(coerce_to_target_type+0x95) [0x56522afc017b]    postgres: david postgres [local] INSERT(+0x237e37) [0x56522afcde37]    postgres: david postgres [local] INSERT(+0x232606) [0x56522afc8606]    postgres: david postgres [local] INSERT(transformExpr+0x3a) [0x56522afc83a2]    postgres: david postgres [local] INSERT(transformExpressionList+0x133) [0x56522afdee38]    postgres: david postgres [local] INSERT(+0x1ecf87) [0x56522af82f87]    postgres: david postgres [local] INSERT(transformStmt+0x96) [0x56522af821ac]    postgres: david postgres [local] INSERT(+0x1ec114) [0x56522af82114]    postgres: david postgres [local] INSERT(transformTopLevelStmt+0x27) [0x56522af8200f]    postgres: david postgres [local] INSERT(parse_analyze+0x73) [0x56522af81e85]    postgres: david postgres [local] INSERT(pg_analyze_and_rewrite+0x49) [0x56522b2c1bcb]    postgres: david postgres [local] INSERT(+0x52c2b5) [0x56522b2c22b5]    postgres: david postgres [local] INSERT(PostgresMain+0x813) [0x56522b2c6895]    postgres: david postgres [local] INSERT(+0x4889d7) [0x56522b21e9d7]    postgres: david postgres [local] INSERT(+0x488111) [0x56522b21e111]    postgres: david postgres [local] INSERT(+0x48469f) [0x56522b21a69f]    postgres: david postgres [local] INSERT(PostmasterMain+0x1283) [0x56522b219e5a]    postgres: david postgres [local] INSERT(+0x395c54) [0x56522b12bc54]    /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f3fc9817b97]    postgres: david postgres [local] INSERT(_start+0x2a) [0x56522ae4d40a]2020-12-14 13:56:28.780 PST [3459] STATEMENT:  INSERT INTO tbl_circle(a) VALUES('( 1 , 1 ) , 5'::circle );

让我们再次用新的十六进制地址字符串运行addr2line命令。

$ addr2line 0x52c2b5 -f -e `which postgres`exec_simple_query/home/david/postgres/src/backend/tcop/postgres.c:1155

现在,我们得到了函数名、文件名和行号。你可以继续这样做,直到你得到分析根本原因所需的全部backtrace信息。

小结

这篇博客简单地讨论了PostgreSQL 13中为开发者引入的一个非常有用的选项。在我的日常开发工作中,我经常使用这个选项,它帮助我快速定位错误。当有人报告一个随机发生的错误时,我也使用这个功能。为了调试这样的问题,我只需用c函数名启用它。当错误再次发生时,我就可以得到确切的backtrace。在追踪错误时,backtrace_functions确实使我的工作更容易。

前文译自:https://www.highgo.ca/2020/12/15/how-to-dump-out-a-backtrace-during-runtime/

后记

PostgreSQL不像Oracle,出了故障需要溯源时,Oracle得益于各种强大的分析工具比如AWR、ASH等,往往在数据库里面就能分析个七七八八,PostgreSQL没有如此强大的功能,需要经常和操作系统打交道,Linux首当其冲。作为DBA,还是需要了解一下常用的工具

编译阶段

1.nm:获取二进制文件包含的符号信息2.strings :获取二进制文件包含的字符串常量3.strip:去除二进制文件包含的符号4.readelf :显示目标文件详细信息5.objdump:尽可能反汇编出源代码6.addr2line:根据地址查找代码行

运行阶段

•gdb:强大的调试工具,必备技能之一•perf:很牛逼的综合性分析工具,大到系统全局性性能,再小到进程线程级别,甚至到函数及汇编级别•systemtap:允许使用者向内核代码或者用户空间的程序设置一个观测点,当内核代码或者用户程序运行到这个观测点时,使用者有机会执行一个自己编写的内核函数,读取该观测点上下文,进行分析与统计。•ldd:显示程序需要使用的动态库和实际使用的动态库•strace:跟踪程序当前的系统调用•ltrace :跟踪程序当前的库函数•time:查看程序执行时间、用户态时间、内核态时间•gprof:显示用户态各函数执行时间•valgrind:检查内存错误•mtrace:检查内存错误,可以检测memory leak

其他

•proc文件系统•系统日志

其中proc下面,我们经常打交道的有meminfo、swaps和kmsg,meminfo可以参考:https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/s2-proc-meminfo,注意匿名页可能的潜在风险。

另外就是/proc/postgres_pids/xxx下面的文件,比如

1./proc/pid/smaps,查看内存使用情况,比如/dev/zero是PostgreSQL的shared_buffer2./proc/pid/fd,查看进程打开的文件3./proc/pid/oom_score,查看被OOM killer干掉的几率4./proc/pid/limits,查看进程的资源限制5./proc/pid/status里面的Cpus_allowed_list和Mems_allowed_list,查看可以在那些CPU上进行调度,CPU亲和性

附一张涨技能的图:

图片

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值