news 2026/10/13 4:11:38

ELF地址反查函数名:从崩溃日志到addr2line的完整实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ELF地址反查函数名:从崩溃日志到addr2line的完整实战指南

程序崩了,日志里只有一行:demo_app[0x55f0a1f11a14]。没有完整调用栈,没有函数名,甚至连模块里的符号都看不出来。这种时候,我脑子里冒出来的第一个动作,从来不是去翻代码,而是把这串运行地址喂给对应的ELF文件,让它输出具体的函数名。从一个ELF文件出发,逆向定位运行地址对应的函数名,听起来像是调试器的精细活,实际操作起来就是几个命令加一点偏移换算的功夫,但里面的坑真不少,尤其当你处理的是PIE、动态库、strip过的发布包时,换算错一位就是白干。

这篇东西不搞理论堆砌,我会从实际日志长什么样讲起,带你走一遍完整的“地址→函数名”流程,把符号表、调试信息、ASLR偏移、strip兜底这些点全串起来。适合服务端开发、嵌入式工程师、性能分析爱好者,以及所有被“一长串十六进制”折磨过的人。

1. 先搞清楚:什么日志会给你一串需要还原的运行地址

1.1 崩溃日志里“地址”的几种长相

不同运行时打出来的地址格式不一样,但核心信息就两类:绝对运行地址、或者“基址+偏移”。先说最常见的 glibc backtrace 格式:

./demo_app(+0x13a14)[0x559e7a7b3a14] /lib/libdemo.so.1(+0x2a3c)[0x7f1e2c3ba3c]

中括号里是整个进程地址空间中的绝对地址,小括号里的+0x13a14是相对模块加载基址的偏移。注意,glibc已经帮你算好了偏移,所以括号里的偏移量可以直接拿来用,这能省掉后面一大半换算工作。

另一种格式来自某些系统库和信号处理器的自绘输出:

Crashed thread: 0 demo_app 0x0000559e7a7b3a14 (in demo_app)

这种只给绝对地址,不给偏移,你得自己从/proc/<pid>/maps里找加载基址做换算。还有一种是 Android/ARM 常见的:

pc 00013a14 /data/app/demo_app/lib/arm64/libdemo.so

pc后面直接跟的是相对于起始地址的偏移。看到这种格式反而要开心,拿到ELF就能直接查。

这些格式虽然长相不同,本质都是在问同一个问题:**这个地址落在代码的哪个函数里?**要回答它,得先知道地址对应的ELF文件在内存里的真实排布,以及ELF内部存了哪些可供定位的符号信息。

1.2 哪些场景真正需要这条“地址→函数名”链路

我整理了下自己实际碰到过的场景,供你对照:

  • 线上崩溃分析:崩溃发生在用户侧或灰度机器,没有现成的coredump,只有日志里打出来的一串地址。这是触发频率最高的场景。
  • coredump事后分析:有core文件,但机器上没有对应的符号文件,需要拿到目标机ELF后离线定位。
  • 性能采样归因:perf record采样后得到一堆采样地址,火焰图的横轴坐标全是十六进制,不解析成函数名根本没法人看。
  • 动态追踪:用uprobe/ftrace跟踪某个内核事件,事件输出中出现用户态进程的IP(指令指针)地址,需要反查。
  • 写自己的信号处理器:想在SIGSEGV处理函数里打印出可读的函数调用栈,而不是干巴巴的地址。

无论是哪种,前提都一样:你要有一份和目标运行版本完全对应的ELF文件。文件对不上,后面一切工具都是白搭,这算是这条链路上的第一铁律。

2. ELF里能让地址变成函数名的信息,藏在哪两部分

定位函数名这件事,靠的是ELF文件内部的两处“地图”信息:符号表(symbol table)和调试信息(debug info)。很多人把这两个概念混在一起,其实它们解决的是不同层次的问题。

2.1 符号表:名字、地址、大小的三列账本

符号表是ELF结构里的一个section,通常叫.symtab或.dynsym。用readelf -sW可以把它打出来,大概是下面这个样子:

Symbol table '.symtab' contains 1234 entries: Num: Value Size Type Bind Vis Ndx Name 42: 0000000000001230 24 FUNC GLOBAL DEFAULT 2 parse_config 47: 0000000000002100 128 FUNC LOCAL DEFAULT 2 parse_option 51: 0000000000005ba0 200 FUNC GLOBAL DEFAULT 2 process_request

每行符号记录了三件关键事情:Value是符号的地址(链接期虚拟地址),Size是函数体长度,Name是函数名。有了这三列,你就可以把地址“夹”在某条符号记录的Value和Value+Size之间,从而判断它落在哪个函数的代码范围内。

但符号表有个先天的限制:它不知道行号。你查到process_request,可你想知道的是它内部第42行崩了,符号表答不上来。而且.symtab在strip之后就没了,发布包大多不留。

2.2 调试信息:真正让你定位到“行号”的宝藏

如果ELF里带着调试信息,情况就完全不一样了。.debug_line这个section里存着一张行表(line table),记录“地址区间→源文件:行号”的映射。编译器带着-g选项编译时,会把这个表格生成出来,反汇编时你能看到每条指令对应源码的哪一行,靠的就是它。

调试信息里还有.debug_info、.debug_abbrev、.debug_str这些section,它们一起描述类型、变量的完整结构。对“地址→函数名”来说,行表是核心,其它是为更高阶的调试能力服务的。这也是为什么有些ELF能查函数名但查不到行号,多半是只保留了符号表,丢了调试信息。

2.3 先给你的ELF做个体检:五大命令一次说清

拿到ELF后,我习惯先跑一组命令摸清底细,再决定后面用什么工具:

file demo_app # 看架构、PIE还是非PIE readelf -S demo_app | grep -E 'debug|symtab' # 看有没有符号表和调试section readelf -sW demo_app # 看符号表内容 readelf -l demo_app | grep LOAD # 看程序头的段布局 nm -n demo_app # 按地址排序的所有符号

其中file的输出尤其值得留意。如果显示ELF 64-bit LSB shared object, x86-64,说明这是个PIE(位置无关可执行文件);如果显示ELF 64-bit LSB executable,通常是非PIE的传统可执行文件。这两种文件的地址换算逻辑完全不同,后面第4章会专门讲。

符号表和调试信息的关系,用下面这张表可以看得很清楚:

对比项符号表(.symtab)调试信息(.debug_line等)
主要作用地址→符号名地址→符号名+行号+变量类型
生成条件链接器默认生成编译加-g选项
strip之后消失消失
占体积较小较大
典型命令nm, readelf -saddr2line的查行号能力依赖它

实际情况里,两者往往同时存在。但你要是手里只有一份生产环境的strip包,符号表和调试信息都别想了,只能走第5章讲的动态符号表路线。

3. 首选武器addr2line:从地址到函数名和行号的最短路径

3.1 单地址查询的基本姿势

有符号信息时,addr2line是效率最高的工具,没有之一。看这条:

addr2line -e demo_app -f -C -p 0x13a14

对应输出可能是:

process_request at src/handler.c:42

这个输出不是变魔法,它内部做的事情就是刚才说过的两步:先用.symtab找地址落在哪个函数,再用.debug_line的行表把函数内偏移精确到源码行号。这也解释了为什么没有调试信息时行号会变成??。

需要特别注意一个常见误区:**addr2line接收的是ELF里的链接期虚拟地址(vaddr)/偏移,不是进程运行时的绝对地址。**除非日志里已经给你+0x13a14这种偏移格式,否则直接拿运行地址填进去大概率返回垃圾结果。地址换算那套逻辑,下一章展开。

3.2 参数速查:被我反复用的那几个

我用得最顺的参数组合是-e -f -C -p -i,逐个说一下:

参数作用踩过坑才知道的价值
-e <elf>指定要查的ELF文件版本不对时,先排查这个
-f显示函数名不加只有一个行号,等于没查
-C自动demangle C++符号不加会输出_ZN12processCtx5ParseEv这种
-ppretty模式,一行输出批量跑日志时省一半篇幅
-i显示内联函数链强优化代码里没有它就是一头雾水
-a显示你输入的地址批量输出排查“对不上号”时很有用
-s只显示basename路径长路径日志看着清爽

C++项目强烈建议-p -f -C一起上。我自己第一次解析一个C++崩溃栈时,忘了加-C,看到一长串_ZN6Server15ProcessRequestEiP8Request愣了半天,加一个-C秒变Server::ProcessRequest(int, Request*),体验天差地别。

3.3 批量解析整份backtrace:一句话脚本

崩溃日志里的backtrace往往几十行,不可能一行行敲命令。我常用下面这个脚本,把日志中的十六进制地址一把梭:

grep -oE '0x[0-9a-f]+' backtrace.txt | while read addr; do echo -n "$addr -> " addr2line -e demo_app -f -C -p "$addr" done

跑完会得到一份“地址→函数名”清清楚楚的映射表。如果日志给的是glibc那种带(+0x13a14)的偏移格式,可以直接提取括号里的偏移,注意补上0x前缀再喂给addr2line:

grep -oE '\(0x[0-9a-f]+\)' backtrace.txt | tr -d '()' | while read off; do echo -n "$off -> " addr2line -e demo_app -f -C -p "0x${off#0x}" done

这里的小细节是:有些老版本addr2line对不带0x前缀的输入不识别,哪怕你给的是纯十六进制字符串。所以脚本里宁可多补一个前缀,也别让它空手而归。

3.4 一个完整的解析示例

假设你抓到这样一段日志:

./app(+0x13a14)[0x55b5c1a11a14] ./app(+0x11040)[0x55b5c1a0f040] /lib/libc.so.6(+0x45dff)[0x7f4e3d1d4dff]

你已经确认app是这个架构、这个版本的最终发布二进制,并且还留着带符号的版本。执行:

addr2line -e ./app -f -C -p 0x13a14 addr2line -e ./app -f -C -p 0x11040

得到:

process_request at src/handler.c:42 send_response at src/io_loop.c:118

第三条是libc的地址,那就得拿系统库的对应符号文件去解析,或者用下一章的maps换算后交给系统仓库里的debug包。整个过程也就一分钟的事。

4. 三个地址的换算:为什么不能拿日志里的数字直接查

4.1 三个“地址”分别是什么

翻阅大量“地址定位失败”的求助帖后我发现,问题几乎都出在同一个地方:把链接地址、加载地址、运行地址三个概念搅在了一起。先把概念钉死:

  • 链接地址(vaddr):链接器生成ELF时就写入符号表和程序头的虚拟地址。它是“设计图纸”上的位置,不随运行变化。
  • 加载地址(加载基址/load_bias):程序或共享库被操作系统装入进程地址空间后,真正映射到的起始地址。受ASLR影响,每次启动都可能不同。
  • 运行地址(运行时绝对地址):在跑的进程里某条指令的真实地址,也就是日志中括号里的那一大串数字。

它们的关系极其简单:

运行地址 = 加载基址 + 偏移

日志里的小括号偏移,本质就是“目标指令在ELF里的链接地址相对第一个LOAD段链接基址的差值”。所以拿到这个偏移,就不用再关心加载基址,直接拿偏移去addr2line里查就行。这就是glibc背戳格式最友好的地方。

4.2 PIE和ASLR:为什么同一段代码每次地址都不同

现在Linux发行版默认开启PIE。PIE产物的特点:链接时基址是0,所有函数的链接地址都特别小,类似0x13a14、0x11040,加载时再随机映射到一个高位地址。如果你只是用file看到shared object,说明它就是PIE,那么日志里如果是绝对地址而非偏移,就一定要先做减法,不能直接拿给addr2line。

非PIE的传统可执行文件(老式ELF executable)链接基址通常固定为0x400000左右,加载地址基本等于链接地址,这时候才能“拿那个地址直接查”。可现在默认都开PIE,我遇到拿绝对地址直接查然后查了个寂寞的情况,九成都是忘了这件事。

另外注意,ASLR只随机化加载基址,不改变文件内各函数的相对偏移。这就意味着:你完全不需要知道基址是多少,只要手里有带符号的ELF,任何一次崩溃的偏移都是可复现的。这背后是内核对ELF的页对齐映射机制,把编译产物当作一个整体搬进内存,只是搬家,不改内部布局。

4.3 拿到加载基址:/proc/ /maps 实战

日志只给绝对地址、没给偏移时,你需要从当时的进程状态里把加载基址捞出来。标准动作是翻/proc/<pid>/maps:

cat /proc/12345/maps | grep demo_app

输出示意:

559e7a7a0000-559e7a7c0000 r-xp 00000000 08:01 2334455 /opt/app/bin/demo_app 559e7a7c0000-559e7a7d0000 r--p 00020000 08:01 2334455 /opt/app/bin/demo_app 559e7a7d0000-559e7a7d2000 rw-p 00030000 08:01 2334455 /opt/app/bin/demo_app

图中第一列是进程内地址范围,第二列是权限,第三列是文件内偏移,最后一列是映射的文件。换算的关键:你的目标地址在哪个区间里,就用哪个区间的起始地址当作加载基址。

假如日志里的绝对运行地址是0x559e7a7b3a14,它落在第一行559e7a7a0000-559e7a7c0000里,偏移就是:

0x559e7a7b3a14 - 0x559e7a7a0000 = 0x13a14

然后正常交给addr2line:

addr2line -e demo_app -f -C -p 0x13a14

如果你要更严格地算加载基址,可以结合ELF程序头的LOAD段信息:加载基址 = 映射起始地址 - LOAD段的p_vaddr。对第一段LOOD来说,PIE的p_vaddr为0,所以映射起始地址就是加载基址,直接用起始地址减即可;对非PIE的传统可执行,p_vaddr通常为0x400000,映射起始地址也恰好在0x400000附近,于是运行地址基本等于链接地址。多数场景下,选对包含目标地址的maps行,再用“运行地址 - 该行起始地址”这个公式,就够用了。

还有个容易翻车的细节:maps必须来自崩溃时刻的进程。进程退出了、maps文件没了,日志里又没有偏移,那绝对地址就再也还原不出来了。这也是为什么我一直强调,线上崩溃日志里必须带上(+0x偏移)这种格式,或者建立快照自动保存maps,否则崩溃分析直接少一条腿。

5. strip之后也有的救:动态符号表与工程化兜底

5.1 动态符号表是最后一张底牌

发布包为了省体积,几乎都会被strip。strip会把.symtab和调试信息全删掉,但有一个section通常还在:.dynsym,动态符号表。它是给动态链接器在运行时解析符号用的,删了就没人能加载这个库,所以动不得。

查看方式:

readelf -sW --dyn-syms demo_app | head -50

输出里能看到所有对外导出的全局函数,比如:

Symbol table '.dynsym' contains 89 entries: Num: Value Size Type Bind Vis Ndx Name 5: 0000000000001230 96 FUNC GLOBAL DEFAULT 12 md5_update 7: 0000000000002100 128 FUNC GLOBAL DEFAULT 12 process_request

局限也很明显:它只包含导出符号,以及链接器需要解析的全局符号。static函数、被-fvisibility=hidden藏起来的内部实现,在.dynsym里是看不到的。所以用动态符号表定位,只能给你一个“大概在哪个导出函数附近”,具体行号想都不用想。

5.2 怎么找“离得最近的函数”

动态符号表里没有行表,定位策略退化为:找到地址区间包含目标地址的那个导出符号。把符号按地址排序,然后沿着地址往上找第一个“符号地址 ≤ 目标地址”的条目就行。

用nm可以直接按地址排序把符号列出来:

nm -D --numeric-sort demo_app

再用个小脚本把目标地址插进符号区间:

TARGET=0x13a14 nm -D --numeric-sort demo_app | awk -v t=$TARGET ' $1 ~ /^[0-9a-f]+$/ && $2 == "T" { addr = strtonum("0x" $1) if (addr <= t) { last_name=$3; last_addr=addr; last_size=strtonum($2)?0:0 } } END { print "closest symbol:", last_name } '

实话实说,这个脚本胜在轻量,但精度有限。更可靠的算法是用二分查找在符号数组里找“上界”,这不难,但要自己写点代码。我偷懒时经常借助gdb,见下节。

5.3 gdb也能干这活,而且比脚本稳

strip的二进制同样能被gdb解析地址,靠的就是动态符号表加运行时的段信息。命令如下:

gdb -q -batch demo_app -ex "info symbol 0x13a14"

输出类似:

process_request + 42 in section .text

+ 42表示在某函数内偏移42字节,这已经够你对照反汇编做进一步判断了。甚至可以直接用gdb反汇编那一小段:

gdb -q -batch demo_app -ex "x/8i 0x13a14"

看一眼附近几条指令,经常能确认到底是哪个分支、哪次调用炸的。这类“粗定位+反汇编”的组合拳,是strip包场景下最现实的方案。

5.4 正确的工程姿势:发布时不要真的丢掉符号

生产环境为了安全省体积要strip,我完全理解,但strip需要可控地做,而不是把符号文件当垃圾扔了。我建议的规范流程是:

  1. 编译期完整保留-g调试信息,生成带全部符号的完整二进制。
  2. 用objcopy把调试信息单独抽成一个文件:
objcopy --only-keep-debug demo_app demo_app.debug
  1. 发布用strip后的瘦身版:
objcopy --strip-debug demo_app demo_app.stripped
  1. crash日志里记录ELF的build-id(readelf -n demo_app可查),把.debug文件按build-id归档,事后用带调试信息的版本做addr2line。

这条链路走通后,即使线上跑的是精简二进制,你手里的.debug文件依然是完整的“设计图纸”,一切原有的符号和行号查询能力全部保留。build-id匹配尤其值得做,它比文件名靠谱的多——同名文件不同版本这种事,我踩的次数太多了。

6. 综合避坑:内联、Thumb、PLT这些附加题

6.1 地址落在内联函数里:别被外层函数误导

-O2以上编译时,编译器经常把一个小函数内联进调用方。崩溃地址会落在调用方函数的代码区间内,但程序计数器其实指向的是被内联进来的小函数。addr2line的-i参数就是为这个场景设计的,它会输出一条内联链:

addr2line -e demo_app -f -C -p -i 0x13a14

输出可能是:

inline_checks at src/validate.c:204 process_request at src/handler.c:156

第一行才是真正发生问题的内联函数,第二行是它的外层调用者。不加-i你只能看到第二行,很容易误判“崩在handler.c第156行”,而实际上问题出在validate.c里的某个条件检查。

6.2 ARM/Thumb地址最低位的陷阱

跑ARM平台时,如果目标地址带最低位为1(比如0x13a15),别急着报错。那是Thumb指令集的产物——ARM处理器用地址最低位表示当前处于Thumb模式,PC值会带这个标志位。查询前先把它屏蔽掉:

addr=0x13a15 addr2line -e demo_app -f -C -p "$(( addr & ~1 ))"

忘了这步,地址会错位一个字节,函数名可能对得上也可能错,行号大概率全乱。这问题在x86上不存在,但在ARM嵌入式交叉开发里出镜率很高。

6.3 地址落在PLT里:查出来是库函数的跳板

当调用 shared 库的外部函数时,main模块里会有一小段PLT(Procedure Linkage Table)代码。如果崩溃地址恰好落在PLT那几字节上,addr2line会告诉你“在.plt里”,完全看不出是哪个外部函数。

这时候需要结合反汇编看这几点在调什么:

objdump -d demo_app | grep -A5 '<.*@plt>'

看到callq 0x7f...或jmp *0x...(%rip),再配合objdump -R demo_app | grep <地址>找重定位条目,很快能定位到实际调用的库函数。说白了,PLT场景下要查的不是“代码在哪”,而是“跳转目标是谁”。

6.4 延伸:自己写一个地址解析器也不难

如果你在交叉编译环境下遇不到addr2line,或者想把这套逻辑集成进自己的工具链,解析思路其实很清晰:读ELF的section header,找到.symtab和.debug_line,然后按地址值遍历符号表,找符号区间覆盖目标地址的那项。用Python的pyelftools可以快速实现一个最小版本:

from elftools.elf.elffile import ELFFile def find_symbol(elf_path, addr): with open(elf_path, 'rb') as f: elf = ELFFile(f) symtab = elf.get_section_by_name('.symtab') if not symtab: print("no .symtab, try .dynsym") symtab = elf.get_section_by_name('.dynsym') for sym in symtab.iter_symbols(): start = sym['st_value'] size = sym['st_size'] if start and start <= addr < start + size: return sym.name return "<unknown>"

这段代码的边界条件很有意思:符号的st_size为0时要跳过,否则会匹配到一堆段边界符号;目标地址要转成ELF里的vaddr,不能直接用运行地址;多个符号同地址时偏好选函数类型的。真正要做得健壮,比这个示例多考虑十来个边界情况,但核心循环就是“地址区间包含”这一个判断。

6.5 踩坑速查表

把上面积累的经验浓缩成一张表,排查时直接对照:

现象常见原因解决动作
函数名对,行号全??有符号表但无调试信息编译加-g,重新生成符号
直接拿绝对地址查,结果没一个对的忘了PIE/ASLR偏移换算先减加载基址,用偏移查询
C++符号全是_ZN乱码没做demangle加-C参数
查到外层函数但和崩溃点对不上内联展开加-i显示内联链
ARM地址对不上Thumb最低位标志地址& ~1
函数名是.plt或??落在PLT或strip包反汇编+查重定位,或换debug文件
崩溃现场没留maps,日志只有绝对地址没有保存进程地址快照日志模板里强制带上偏移或maps

这张表基本覆盖了我在实际处理“运行地址定位函数名”时遇到的九成麻烦。


最后说一个我养成的习惯:每次发版,除了二进制,我一定会把.debug文件、当时的/proc/<pid>/maps样本、以及readelf -n的build-id一起归档进发布包管理目录。你永远不知道线上什么时候会给你甩来一个只有十六进制地址的崩溃日志,而提前把符号资产归档好,是唯一能保证那时你还可以从容解析的先手棋。遇到跨版本问题,先查build-id对不对再谈解析,这能帮你省下至少一个下午。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 4:11:06

OAuth 2.1 + FIDO2 无密码认证实战:原理、落地与安全升级指南

先说一个让我印象特别深的场景&#xff1a;某次线上故障复盘&#xff0c;A同学发现自家应用的用户数据库里&#xff0c;有三成账号的口令哈希已经能被彩虹表直接拆穿。更麻烦的是&#xff0c;攻击者根本不碰数据库——他只需要仿冒一个登录页面&#xff0c;用户把账号和密码填进…

作者头像 李华
网站建设 2026/10/13 4:10:03

VLAN+MSTP+链路聚合综合实验:二层冗余与负载分担配置详解

最近一直在练网络竞赛里很常见的二层综合实验&#xff0c;题目把VLAN、MSTP、链路聚合三个知识点放在同一个拓扑下。说句实话&#xff0c;单个知识点单独学&#xff0c;感觉都不难&#xff0c;可真把它们揉在一起配置的时候&#xff0c;交互关系才是最容易翻车的地方。这篇文章…

作者头像 李华
网站建设 2026/10/13 4:09:56

SpringBoot+Vue新闻发布信息收集系统全栈开发实战

每年这时候都会有一大批同学被管理系统类项目折腾得够呛。前几天还有人问我“新闻发布信息收集系统”到底该从哪下手&#xff0c;我看了下市面上的资料&#xff0c;要么讲得太空不讲落地&#xff0c;要么代码片段东一块西一块根本拼不起来。这个选题看起来普通&#xff0c;但真…

作者头像 李华
网站建设 2026/10/13 4:09:56

Linux中文输入法配置全攻略:框架选型、环境变量与常见排错

我印象里&#xff0c;凡是刚跳进Linux的人&#xff0c;十有八九会在“中文输入法”这一关上卡住一阵子。明明装好了包&#xff0c;CtrlSpace按到屏幕发亮也无动于衷&#xff0c;注销重登、重启机器都试过&#xff0c;最后还是没能打出第一个汉字。后来我才明白&#xff0c;Linu…

作者头像 李华
网站建设 2026/10/13 4:09:13

BIO/NIO/AIO核心差异与手写NIO多路复用服务器实战

这个系列的上一篇&#xff0c;我把BIO的线程模型和它为什么撑不住高并发讲透了。这篇接着往下聊&#xff1a;NIO到底改了什么&#xff0c;AIO又是怎么回事&#xff0c;以及一个真实的、可以用在项目里的NIO多路复用服务器应该怎么手写。如果你面试被问到BIO/NIO/AIO的区别&…

作者头像 李华
网站建设 2026/10/13 4:09:02

比特币地址与签名全解:从P2PKH到Taproot与PSBT

说到比特币地址类型和签名方式&#xff0c;这几年真的可以用“三年一小变&#xff0c;五年一大变”来形容。早期做开发只需要记住1开头和3开头两种地址&#xff0c;后来隔离见证把bc1q带到台前&#xff0c;再后来Taproot又贡献了一个bc1p。很多人写钱包或者做离线签名时&#x…

作者头像 李华