你可能也需要试试DWARF
DWARF 是一种调试信息格式,通常用于源码级别调试
相关资料比较琐碎, 整理给大家, 希望大家可以用得上
如没有特殊说明, 命令执行环境为 OS X
什么是 DWARF ?
- DWARF 第一版发布于 1992 年, 主要是为UNIX下的调试器提供必要的调试信息,例如PC地址对应的文件名及行号等信息,以方便源码级调试
- 其包含足够的信息以供调试器完成特定的一些功能, 例如显示当前栈帧(Stack Frame)下的局部变量, 尝试修改一些变量, 直接跳到函数末尾等
- 有足够的可扩展性,可为多种语言提供调试信息: 如: Ada, C, C++, Fortran, Java, Objective C, Go, Python, Haskell ...
- 除了编译/调试器外,还可用于从运行时地址还原源码对应的符号|行号的工具(如: atos)
目前 DWARF 已经在类UNIX系统中逐步替换 stabs(symbol table strings) 成为主流的调试信息格式。
使用GCC或者LLVM系列编译器都可以很方便生成DWARF调试信息。
下面是其发展历史:
DWARF版本 | 年份 | 组织 | 详情 |
---|---|---|---|
DWARF1 | 1992 | Unix SVR4, PLSIG, UnixInternational | 贝尔实验室 1988 年开发用于SVR4, 用于C compiler & SDB, PLSIG 和 Unix International在1992年为其编写了文档, 缺点: 结构不紧凑 |
DWARF2 | 1993 | PLSIG | PLSIG 做了一些优化(主要是减少调试信息大小),于1993年发布了DWARF 2 草案。但是由于摩托罗拉的 88000 处理器出现了一些严重故障,导致了一家名为 Open88 的公司破产, 而 Open88 公司是 PLSIG 和 UnixInternational 的金主, 随后这两个组织随即解散, DWARF 2 正式标准就从未发布。 |
DWARF3 | 2005 | Free Standards Group | 因为在 HP/Intel IA64 architecture 架构及 C++ ABI 方面有更好的表现, Free Standards Group 在 2003 年完成了草案并在2005年发布了正式标准 |
DWARF4 | 2010 | DWARF Debugging Format Committee | Free Standards Group 和 Open Source Development Labs 在 2007 年合并为了著名的 Linux Foundation, DWARF委员会随后以独立方式存在并创建了网站 dwarfstd.org, 进行了一系列的更新后(支持LVIM, 对 Type Description存储的优化等),于2010 年发布 DWARF4 |
DWARF5 | 2017 | DWARF Debugging Format Committee | 在经过六年的开发后, DWARF委员会于 2017 年发布了 DWARF5 标准, 主要特性有: 更优的数据压缩, 调试信息从可执行文件的分离, 对宏定义有更好支持, 更快的搜索符号(symbol) |
下面列出DWARF的一些竞品,方便大家更了解调试格式的发展
格式 | 年份 | 典型使用环境 |
---|---|---|
stabs (symbol table strings) | 1981 | 广泛使用于 Unix 环境, 目前已经逐步被 DWARF 替换 |
COFF (Common Object File Format) | 1983 | UNIX System V(AT&T), AIX(IBM, XCOFF), DEC、SGI(ECOFF) |
IEEE695 | 1990 | 虽是 IEEE 标准, 但是支持少数几种处理器架构, 目前已经基本消亡 |
PECOFF (PE/COFF) | 1995(?) | Windows PE, Windows NT, COFF 最流行的变种 |
OMF (Object Module Format) | 1995(?) | CP/M, DOS, OS/2, embedded systems |
PDB(Program database) | ? | Windows, Microsoft Visual Studio 的默认调试格式 |
我们为什么需要 DWARF ?
上面我们提到DWARF主要为调试器(debugger)服务,我们通过实现一个调试器Demo,帮助大家了解整个技术链。
首先,我们看下一般调试器的样子:
下面的例子源于 How debuggers work
这个简单的调试器只完成一个功能: 打印被调试程序运行时执行的所有指令
下面的例子使用了 Linux ptrace系统调用, 虽然我很想用 Mac 做演示, 但是 ptrace(OS X) 在Mac上已经被阉割了, 缺少 GETREGS 这种关键的 ptrace 功能,
而使用 thread_get_state() 又过于复杂, 所以 ...
体验编写一个调试器-Demo
- 启动 Linux 容器(如果本地没有Linux环境)
docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \
-it harisekhon/ubuntu-dev /bin/bash
- 创建工作目录, 创建 被调试程序hello.c
mkdir ptrace-example-mac; cd ptrace-example-mac
cat > hello.c <<EOF
#include <stdio.h>
int main() {
printf("Hello, world!?\n");
return 0;
}
EOF
- 创建调试器Demo main.c
cat > main.c <<EOF
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <unistd.h>
void procmsg(const char* format, ...);
void run_target(const char* programname);
void run_debugger(pid_t child_pid);
int main(int argc, char** argv)
{
pid_t child_pid;
if (argc < 2) {
fprintf(stderr, "Expected a program name as argument\n");
return -1;
}
child_pid = fork();
if (child_pid == 0)
run_target(argv[1]);
else if (child_pid > 0)
run_debugger(child_pid);
else {
perror("fork");
return -1;
}
return 0;
}
/* Print a message to stdout, prefixed by the process ID
*/
void procmsg(const char* format, ...)
{
va_list ap;
fprintf(stdout, "[%d] ", getpid());
va_start(ap, format);
vfprintf(stdout, format, ap);
va_end(ap);
}
void run_target(const char* programname)
{
procmsg("target started. will run '%s'\n", programname);
/* Allow tracing of this process */
if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) {
perror("ptrace");
return;
}
/* Replace this process's image with the given program */
execl(programname, programname, (char *)0);
}
void run_debugger(pid_t child_pid)
{
int wait_status;
unsigned icounter = 0;
procmsg("debugger started\n");
/* Wait for child to stop on its first instruction */
wait(&wait_status);
while (WIFSTOPPED(wait_status)) {
icounter++;
struct user_regs_struct regs;
ptrace(PTRACE_GETREGS, child_pid, 0, ®s);
unsigned instr = ptrace(PTRACE_PEEKTEXT, child_pid, regs.rip, 0);
procmsg("icounter = %u. IP = 0x%08x. instr = 0x%08x\n",
icounter, regs.rip, instr);
/* Make the child execute another instruction */
if (ptrace(PTRACE_SINGLESTEP, child_pid, 0, 0) < 0) {
perror("ptrace");
return;
}
/* Wait for child to stop on its next instruction */
wait(&wait_status);
}
procmsg("the child executed %u instructions\n", icounter);
}
EOF
- 编译
gcc -O0 hello.c -o hello # 编译被调试程序
gcc main.c -o ptrace-example-linux # 编译调试器 demo
编译完成后工作目录如下:
root@9e0370a08026:/ptrace-example-mac# ll
total 40
drwxr-xr-x 2 root root 4096 Apr 21 05:19 ./
drwxr-xr-x 1 root root 4096 Apr 21 05:14 ../
-rwxr-xr-x 1 root root 8600 Apr 21 05:18 hello*
-rw-r--r-- 1 root root 81 Apr 21 05:17 hello.c
-rw-r--r-- 1 root root 1888 Apr 21 05:17 main.c
-rwxr-xr-x 1 root root 9248 Apr 21 05:19 ptrace-example-linux*
- 执行
./ptrace-example-linux hello # 执行 Hello 并打印Hello运行过程中执行的指令数
运行结果如下:
...
[60] icounter = 141731. IP = 0x3818271a. instr = 0x00e7b841
[60] icounter = 141732. IP = 0x38182720. instr = 0x00003cbe
[60] icounter = 141733. IP = 0x38182725. instr = 0x0f6619eb
[60] icounter = 141734. IP = 0x38182740. instr = 0x44d78948
[60] icounter = 141735. IP = 0x38182743. instr = 0x0fc08944
[60] icounter = 141736. IP = 0x38182746. instr = 0x3d48050f
[60] the child executed 141736 instructions
调试器总结
从上面的调试器Demo执行的结果及源码看, 调试器从 ptrace
系统调用拿到的信息有
- IP: 指令指针寄存器, 表示CPU当前执行的指令在内存中的地址
- instruction: 当前CPU执行的指令内容
- regs: 部分寄存器
- PEEKTEXT: 可在程序内存空间取值
- ...
但从我们平时使用的调试器提供给我们更多的信息:
- 当前程序执行指令对应于源码的文件及行号
- 当前程序栈帧(stack frame|activation record) 下的局部变量
- ...
调试器如何从一些十分基础的信息,例如 IP(指令地址), 呈现给我们如此丰富的调试信息呢?
那便我们为什么需要 DWARF, 其提供了程序运行时信息(Runtime)到源码信息的映射(Source File)
IP|regs|address >>> Source File
(Runtime) DWARF
认识 DWARF
使用 GCC 生成 DWARF 调试信息
认识 DWARF 的第一步便是如何生成 DWARF 信息, 所幸这个过程非常简单
- 对于 GCC 及 CLang 编译器, 使用参数
-gdwarf-4
即可生成 DWARF4 调试信息
我们使用一个名为 foo.c
的示例程序来演示 生成并探索 DWARF 内容
- 创建工作目录及 foo.c 文件
mkdir show-dwarf; cd show-dwarf;
cat > foo.c <<EOF
int foo(int a, int b) {
int c;
static double d = 5.0;
c = a + b;
return c;
}
int main() {
int r;
r = foo(2, 3);
return 0;
}
EOF
- 使用 GCC 编译并生成 DWARF4 编译信息
gcc -O0 -gdwarf-4 foo.c -o foo
编译完成后, 会发现多了 foo.dSYM 的目录, 当前工作目录文件列表如下:
masterkang: ~/Documents/show-dwarf > ll
total 32
-rwxr-xr-x 1 masterkang staff 8728 4 21 13:58 foo
-rw-r--r-- 1 masterkang staff 153 4 21 13:57 foo.c
drwxr-xr-x 3 masterkang staff 96 4 21 13:58 foo.dSYM
-
使用 lldb 调试 foo 并观察 foo.dSYM 是否起作用
-
lldb foo
启动 lldb 并设置被调试程序为 foo - 在 lldb 交互命令输入:
b foo
设置函数 foo 为断点 - 在 lldb 交互命令输入:
run
开始运行被调试程序
调试器稍后之后会在 foo 函数停下来并且打印出对应的源码位置
-
masterkang: ~/Documents/show-dwarf > PATH=/usr/bin lldb foo
(lldb) target create "foo"
Current executable set to 'foo' (x86_64).
(lldb) b foo
Breakpoint 1: where = foo`foo + 10 at foo.c:4, address = 0x0000000100000f6a
(lldb) run
Process 96385 launched: '/Users/masterkang/Documents/show-dwarf/foo' (x86_64)
Process 96385 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100000f6a foo`foo(a=2, b=3) at foo.c:4
1 int foo(int a, int b) {
2 int c;
3 static double d = 5.0;
-> 4 c = a + b;
5 return c;
6 }
7
Target 0: (foo) stopped.
(lldb)
- 进入到 foo.dSYM 目录并且找到调试信息文件
foo
cd foo.dSYM/Contents/Resources/DWARF
masterkang: ~/Documents/show-dwarf/foo.dSYM/Contents/Resources/DWARF > ll
total 24
-rw-r--r-- 1 masterkang staff 9166 4 21 13:58 foo
- 使用 file 命令查看 foo 文件描述
masterkang: ~/Documents/show-dwarf/foo.dSYM/Contents/Resources/DWARF > file foo
foo: Mach-O 64-bit dSYM companion file x86_64
从描述看, 可以看到这是一个 Mach-O
文件
- 既然是
Mach-O
文件, 使用size
命令查看foo
可执行文件包含的 Segment 和 Section
masterkang: ~/Documents/show-dwarf/foo.dSYM/Contents/Resources/DWARF > size -x -m -l foo
Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x1000 (vmaddr 0x100000000 fileoff 0)
Section __text: 0x4b (addr 0x100000f60 offset 0)
Section __unwind_info: 0x48 (addr 0x100000fac offset 0)
total 0x93
Segment __DATA: 0x1000 (vmaddr 0x100001000 fileoff 0)
Section __data: 0x8 (addr 0x100001000 offset 0)
total 0x8
Segment __LINKEDIT: 0x1000 (vmaddr 0x100002000 fileoff 4096)
Segment __DWARF: 0x1000 (vmaddr 0x100003000 fileoff 8192)
Section __debug_line: 0x68 (addr 0x100003000 offset 8192)
Section __debug_pubnames: 0x29 (addr 0x100003068 offset 8296)
Section __debug_pubtypes: 0x25 (addr 0x100003091 offset 8337)
Section __debug_aranges: 0x40 (addr 0x1000030b6 offset 8374)
Section __debug_info: 0xba (addr 0x1000030f6 offset 8438)
Section __debug_abbrev: 0x78 (addr 0x1000031b0 offset 8624)
Section __debug_str: 0x78 (addr 0x100003228 offset 8744)
Section __apple_names: 0x74 (addr 0x1000032a0 offset 8864)
Section __apple_namespac: 0x24 (addr 0x100003314 offset 8980)
Section __apple_types: 0x72 (addr 0x100003338 offset 9016)
Section __apple_objc: 0x24 (addr 0x1000033aa offset 9130)
total 0x3ce
total 0x100004000
可以看到有一个名为 __DWARF
的 Segment, 下面包含 __debug_line
, __debug_pubnames
, __debug_pubtypes
... 等很多歌Section。
这些 Section 便是 DWARF 在 .dSYM 中的存储方式,如何观察这些 Section 的内容呢?
- 使用 dwarfdump 探索 DWARF 内容
输入命令 dwarfdump foo --debug-info
可展示 __debug_line Section 下的内容(非原始内容, 已经经过格式化处理方便查看)
下一小节我们会详细说明 DWARF info 段
下面的这段内容我们会反复引用,下文称之为 DWARF info 示例
masterkang: ~/Documents/show-dwarf/foo.dSYM/Contents/Resources/DWARF > dwarfdump foo --debug-info
----------------------------------------------------------------------
File: foo (x86_64)
----------------------------------------------------------------------
.debug_info contents:
0x00000000: Compile Unit: length = 0x000000b6 version = 0x0004 abbr_offset = 0x00000000 addr_size = 0x08 (next CU at 0x000000ba)
0x0000000b: TAG_compile_unit [1] *
AT_producer( "Apple LLVM version 9.1.0 (clang-902.0.39.1)" )
AT_language( DW_LANG_C99 )
AT_name( "foo.c" )
AT_stmt_list( 0x00000000 )
AT_comp_dir( "/Users/masterkang/Documents/show-dwarf" )
AT_low_pc( 0x0000000100000f60 )
AT_high_pc( 0x0000004b )
0x0000002a: TAG_subprogram [2] *
AT_low_pc( 0x0000000100000f60 )
AT_high_pc( 0x00000018 )
AT_frame_base( rbp )
AT_name( "foo" )
AT_decl_file( "foo.c" )
AT_decl_line( 1 )
AT_prototyped( true )
AT_type( {0x000000b2} ( int ) )
AT_external( true )
0x00000043: TAG_variable [3]
AT_name( "d" )
AT_type( {0x00000083} ( double ) )
AT_decl_file( "foo.c" )
AT_decl_line( 3 )
AT_location( [0x0000000100001000] )
0x00000058: TAG_formal_parameter [4]
AT_location( fbreg -4 )
AT_name( "a" )
AT_decl_file( "foo.c" )
AT_decl_line( 1 )
AT_type( {0x000000b2} ( int ) )
0x00000066: TAG_formal_parameter [4]
AT_location( fbreg -8 )
AT_name( "b" )
AT_decl_file( "foo.c" )
AT_decl_line( 1 )
AT_type( {0x000000b2} ( int ) )
0x00000074: TAG_variable [5]
AT_location( fbreg -12 )
AT_name( "c" )
AT_decl_file( "foo.c" )
AT_decl_line( 2 )
AT_type( {0x000000b2} ( int ) )
0x00000082: NULL
0x00000083: TAG_base_type [6]
AT_name( "double" )
AT_encoding( DW_ATE_float )
AT_byte_size( 0x08 )
0x0000008a: TAG_subprogram [7] *
AT_low_pc( 0x0000000100000f80 )
AT_high_pc( 0x0000002b )
AT_frame_base( rbp )
AT_name( "main" )
AT_decl_file( "foo.c" )
AT_decl_line( 8 )
AT_type( {0x000000b2} ( int ) )
AT_external( true )
0x000000a3: TAG_variable [5]
AT_location( fbreg -8 )
AT_name( "r" )
AT_decl_file( "foo.c" )
AT_decl_line( 9 )
AT_type( {0x000000b2} ( int ) )
0x000000b1: NULL
0x000000b2: TAG_base_type [6]
AT_name( "int" )
AT_encoding( DW_ATE_signed )
AT_byte_size( 0x04 )
0x000000b9: NULL
DWARF 核心: info section
info section 是DWARF的核心,其用来描述程序结构
如果让你去描述上文我们使用的 foo.c 你会怎么表达呢 ?
- 这是一个C语言程序
- 包含一个名为 foo 的函数, 这个函数有两个形式参数, 有符号整型 a 和 有符号整型 b
- foo 函数内声明了一个局部变量: 有符号整型 c, 一个全局变量 d, 其初始化值为 5.0
- foo 函数返回值为局部变量 c 的值, 类型为有符号整型
- 包含一个名为 main 的函数, 其调用了 foo 函数并把返回值存储在了名为 r 的局部变量
- main 函数返回值为有符号整型 0
- foo 函数位于 foo.c 第 1 行
- main 函数位于 foo.c 第 8 行
是不是感觉整个描述很繁琐并且有很多种类型的东西需要描述?
包括函数、形式参数、局部变量、全局变量甚至还有每个函数位于代码的什么位置。
为此 DWARF 提出了 The Debugging Information Entry (DIE) 来以统一的形式描述这些信息,详见: 「DWARF4 2.1」
每个DIE包含:
一个 TAG 属性表达描述什么类型的东西, 如: TAG_subprogram(函数)、TAG_formal_parameter(形式参数)、TAG_variable(变量)、TAG_base_type(基础类型)
所有的 TAG 列表位于: 「DWARF4 Figure 1. Tag names」-
N 个属性(attribute), 用于具体描述一个DIE, 例如
DWARF info 示例
中对函数 foo 的描述:0x0000002a: TAG_subprogram [2] * AT_low_pc( 0x0000000100000f60 ) AT_high_pc( 0x00000018 ) AT_frame_base( rbp ) AT_name( "foo" ) AT_decl_file( "foo.c" ) AT_decl_line( 1 ) AT_prototyped( true ) AT_type( {0x000000b2} ( int ) ) AT_external( true )
AT_low_pc
,AT_high_pc
分别代表函数的 起始/结束 PC地址AT_frame_base
表达函数的栈帧基址(frame base) 为寄存器rbp
的值AT_name
描述函数的名字为 fooAT_decl_file
说这个函数在 foo.c 文件中声明AT_decl_line
说这个函数在 foo.c 第一行声明AT_prototyped
为一个 Bool 值, 为 True 时代表这是一个子程序/函数(subroutine)AT_type
属性描述这个函数返回值的类型是什么, 对于 foo 函数来说, 为 intAT_external
Bool值, 这个函数是否为全局可访问
值得注意的是:
- DWARF 只有有限种类的属性, 全部属性的列表位于: 「DWARF4 Figure 2. Attribute names」
- AT_low_pc 和 AT_high_pc 描述的机器码地址 不等价于程序在运行时的地址,操作系统处于安全因素, 会应用一种地址空间布局随机化的技术Address Space Layout Randomization(ASLR),
加载可执行文件到内存时,会做一个随机偏移(slide),我们获取到偏移后便可还原运行时地址到DWARF地址
组合使用 DIE 就可以描述整个程序,就像搭乐高积木一样(可能需要更多的技巧)
事实上, 类似于编译器语法树,DWARF 使用 Tree
作为组合 DIE 的方式
一个 DIE 可以包含几个 子DIE
, 正如一个文件可以有 N 个函数, 一个函数可以包含 X 个形式参数和 Y 个局部变量
从 DWARF info 示例
也可以看出, DIE 间用空行分割,并且为缩进分明的树形结构。
DWARF4 标准阅读指引
如果你觉得 DWARF 只是用来描述下你的程序有哪些函数和变量,那就 Too Young, Too Naive ~
如果你觉得 DWARF 只是用来描述下你的程序有哪些函数和变量,那就 Too Young, Too Naive ~
DWARF4 标准从这里可以获取 DWARF4.pdf, 建议下载到本地慢慢读
DWARF5 也已经开始得到编译器支持, 详情见 DWART Stand
DWARF4 标准全文 300页左右, 其中正文到 189页,剩余为附录
重要的内容前面有 【重要】标签提示
1. INTRODUCTION...........................................1
介绍 DWARF 目标适用范围,版本变更细节
2. GENERAL DESCRIPTION ...................................7
DWARF 基础概念的描述
2.1 THE DEBUGGING INFORMATION ENTRY (DIE) ............7
【重要】描述了 DIE 的概念
2.2 ATTRIBUTE TYPES ..................................7
【重要】描述了 Atrribute 的概念, 列出了所有的 Attibute, 以及 Attribute 的值可以拥有哪些数据类型
「DWARF4 Figure 3. Classes of attribute value」
2.3 RELATIONSHIP OF DEBUGGING INFORMATION ENTRIES ...16
DIE 间的关系, 为树形结构
2.4 TARGET ADDRESSES.................................16
关于目标机器地址为32或者是64位的一些描述
2.5 DWARF EXPRESSIONS ...............................17
【重要】DWARF 定义出了一种专用的表达式(Expressions), 由操作码和操作数序列组成一个表达式, 最终可计算出想要表达的值。
主要为了下一节 LOCATION DESCRIPTIONS 服务
2.6 LOCATION DESCRIPTIONS............................25
【重要】描述如何计算函数 `stack frame` | `return address` 的值
利用DWARF Expression 来描述 Location, 甚至可以满足下面的情况: 生命周期中,对象的地址甚至是可以改变的
包括: Single location descriptions 和 Location lists 两种
2.7 TYPES OF PROGRAM ENTITIES .......................32
DW_AT_type 属性的描述
2.8 ACCESSIBILITY OF DECLARATIONS....................32
DW_AT_accessibility 属性的描述
2.9 VISIBILITY OF DECLARATIONS.......................33
DW_AT_visibility 属性的描述
2.10 VIRTUALITY OF DECLARATIONS .....................33
DW_AT_virtuality 属性的描述(目前看来仅适用于 C++虚函数)
2.11 ARTIFICIAL ENTRIES..............................34
DW_AT_artificial 属性的描述, 为一个 flag, 当一个对象|类型是由编译器而不是源代码生成时, 这个flag为 True
2.12 SEGMENTED ADDRESSES ............................34
【重要】DW_AT_segment, 一些系统的内存地址可能不是一个平整的地址空间,而是 Segment基址加上一个Offset
当 DW_AT_segment 出现时, 任何计算结果为地址的属性,例如 DW_AT_low_pc, DW_AT_high_pc, DW_AT_ranges or DW_AT_entry_pc
计算出来的值都需要加上 DW_AT_segment 计算所得到的值
2.13 NON-DEFINING DECLARATIONS AND COMPLETIONS ......35
DWARF 中大多是DIE都是用来描述一个对象的定义(Define),但是C语言允许使用 extern 关键字描述这个变量在其他地方定义,
这里仅仅声明(NON-DEFINING DECLARATIONS)一下,这时候 DW_AT_declaration 属性作为 flag 说明这是否为一个非定义声明
2.14 DECLARATION COORDINATES ........................36
【重要】描述对象在源码中的声明位置,包括属性: DW_AT_decl_file, DW_AT_decl_line, DW_AT_decl_column
2.15 IDENTIFIER NAMES ...............................36
【重要】DW_AT_name 的描述, 关于变量的名字, 函数的名字之类的
2.16 DATA LOCATIONS AND DWARF PROCEDURES.............37
【重要】DW_AT_location 属性的描述, 其值为一个 DWARF 表达式,通常用来描述变量或者形式参数的Location
2.17 CODE ADDRESSES AND RANGES ......................37
【重要】很多实体(entry), 如函数, 编译单元,代码块,Try/Catch 等都可以映射到机器码地址(地址范围),
DW_AT_low_pc, DW_AT_high_pc, DW_AT_ranges, DW_AT_start_scope 属性用来描述这些信息
2.18 ENTRY ADDRESS ..................................40
对于有一个地址范围的实体,DW_AT_entry_pc 属性描述执行入口地址在哪里, 如果没有声明, 那么 DW_AT_low_pc 便是入口地址
2.19 STATIC AND DYNAMIC VALUES OF ATTRIBUTES ........40
描述了一些属性的值可能是静态,也可能是动态的情况
2.20 ENTITY DESCRIPTIONS.............................41
DW_AT_description 属性的描述, 用来对实体的注解性描述
2.21 BYTE AND BIT SIZES..............................41
【重要】用来描述实体所需的存储大小, DW_AT_byte_size|DW_AT_bit_size 分别以字节和比特作为单位,
DW_AT_byte_stride|DW_AT_bit_stride 用于描述Array一个元素占用存储大小
2.22 LINKAGE NAMES ..................................41
【重要】因为一些语言,例如C++,允许多个实体使用相同的名字(如参数个数不同的话,函数名字可以一样),但在链接(Link)期间,必须保证没有相同的名字,
所以这些语言的编译器会使用一种成为符号重整(mangled names)的方式来对名字做一些格式化来满足不重名的要求。
DW_AT_linkage_name 用来记录重整过后的名字
3. PROGRAM SCOPE ENTRIES.................................43
程序不同层级实体的描述(编译、模块、函数),但是不包括类型。简单来说,就是存在于二进制程序中 TEXT 段的那些实体
3.1 UNIT ENTRIES......................................43
【重要】Unit Entries 是处在食物链顶端的 DIE (/偷笑),一个可执行文件通常包含一个或多个编译单元(compilation unit),后面会细说
3.2 MODULE, NAMESPACE AND IMPORTING ENTRIES ..........48
DW_TAG_module 为编程语言 module 实体的TAG(如Python), DW_TAG_namespace 是命名空间的TAG(C++)
DW_TAG_imported_declaration | DW_TAG_imported_module 说明此实体被其他地方引用
3.3 SUBROUTINE AND ENTRY POINT ENTRIES ...............53
【重要】描述了大多数语言都会支持的子程序/函数(Subroutine)实体,不同语言差异性比较大... 这部分描述用了整整 11 页
3.4 LEXICAL BLOCK ENTRIES.............................65
语句块(lexical block) 实体相关描述, 语句块可能会有关联的地址范围属性,如 DW_AT_low_pc、DW_AT_high_pc 或者是 DW_AT_ranges
3.5 LABEL ENTRIES ....................................65
LABEL 语句相关描述,TAG 为 DW_TAG_label, label 一般和 goto 配合使用。
3.6 WITH STATEMENT ENTRIES............................66
WITH 语句相关描述,TAG 为 DW_TAG_with_stmt,Pascal / Modula-2 支持 with 语句, 但是这个 with 和 Python 中的 with 是完全不一样的哦
3.7 TRY AND CATCH BLOCK ENTRIES ......................66
try ... catch ... 语句相关描述, TAG 为 DW_TAG_try_block
4. DATA OBJECT AND OBJECT LIST ENTRIES ..................69
程序不可分割的数据实体描述,例如变量、形式参数、常量
4.1 DATA OBJECT ENTRIES...............................69
【重要】对 DW_TAG_variable、DW_TAG_formal_parameter、DW_TAG_constant 相关属性的描述
4.2 COMMON BLOCK ENTRIES..............................73
看起来是 Fortran 的独有特性, fortran 允许一些变量不经形式参数传递到另一个编译单元的函数中,
使用 COMMON 语句标识出这些变量即可,类似于 C 语言定义在函数外边的全局变量
4.3 NAMELIST ENTRIES .................................73
看起来是 Fortran 的独有特性, 类似于 C 语言 struct,组合多个变量方便使用
5. TYPE ENTRIES .........................................75
描述了程序中经常用到的数据类型
5.1 BASE TYPE ENTRIES ................................75
【重要】编程语言定义的基础数据类型, TAG 为 DW_TAG_base_type,描述了可用于描述 Type 的各个属性
5.2 UNSPECIFIED TYPE ENTRIES..........................80
也没看懂到底啥意思,举的例子关于如何描述C语言的变量修饰符。
5.3 TYPEDEF ENTRIES ..................................82
C/C++ 中的 typedef 语句, TAG 为 DW_TAG_typedef
5.4 ARRAY TYPE ENTRIES ...............................83
...
5.15 TEMPLATE ALIAS ENTRIES ..........................103
各种数据类型的描述,如 ARRAY、STRUCT、CLASS、INTERFACE、SUBROUTINE ...
6. OTHER DEBUGGING INFORMATION...........................105
未表示为DIE的调试信息, 即不存储于 `info` 和 `types` section
6.1 ACCELERATED ACCESS ...............................105
【重要】快捷查找符号/Address的能力,包括:
- Lookup by Name: 快速查找全局对象/变量对应的编译单元, 存储于 `pubnames` & `pubtypes` section
- Lookup by Address: 快速定位地址对应的编译单元, 存储于 `aranges` section
6.2 LINE NUMBER INFORMATION ..........................108
【重要】这可谓是相当重要啊,源码级调试就靠它了,存储在 `info` section
DWARF为了节省需要的存储,使用了特殊定义的状态机(Line Number Program)
来生成LINE TABLE,完成 Address 到文件名、行号的映射
6.3 MACRO INFORMATION.................................123
C/C++ 宏定义相关的描述
6.4 CALL FRAME INFORMATION ...........................126
【重要】调用栈相关信息,存储在 `frame` section,典型应用场景为 异常发生时堆栈回溯(stack unwinding)
7. DATA REPRESENTATION ..................................139
这一节描述如何将调试信息序列化为二进制文件
APPENDIX A -- ATTRIBUTES BY TAG VALUE (INFORMATIVE)......191
【重要】描述了不同 DIE 一般会拥有的属性
APPENDIX B -- DEBUG SECTION RELATIONSHIPS (INFORMATIVE)..213
【重要】描述了不同DWARF section 之间的关系
APPENDIX C -- VARIABLE LENGTH DATA: ENCODING/DECODING (INFORMATIVE)...217
LEB128 编码算法
APPENDIX D -- EXAMPLES (INFORMATIVE) ....................219
各种示例
重要的有下面几个
D.1 COMPILATION UNITS AND ABBREVIATIONS TABLE EXAMPLE ...219
D.5 LINE NUMBER PROGRAM EXAMPLE .........................237
D.6 CALL FRAME INFORMATION EXAMPLE ......................239
APPENDIX E -- DWARF COMPRESSION AND DUPLICATE ELIMINATION (INFORMATIVE)..63
【重要】DWARF 压缩及减少冗余的措施
DWARF Sections
DWARF 各个 section 可以有独立的版本号, 下面是各个 Section 及其在不同 DWARF 版本中对应的版本号
Section 间的关系如下:
info
& types
是毫无疑问的宇宙中心,描述了程序结构已经类型声明。
为了支持快速 Name/Type/Address 的查找,pubnames
& pubtypes
& aranges
可快速定位到编译单元
为了减少 info 段所需存储,abbrev
统一描述了不同 DIE 所包含的属性,info 仅需要引用 abbrev
中的 id 就好
为了减少 info 段所需存储,str
用于复用字符串,避免字符串重复,DW_FORM_strploc
存储了 Location 表达式,DW_AT_locationranges
存储了 地址范围表达式,DW_AT_rangesmacinfo
存储了 宏定义相关的信息,DW_AT_macinfoline
存储了行号相关信息, DW_AT_stmt_list 属性指向编译单元对应的 Line Table
开发 DWARF 相关工具
如果你不是编译器/调试器开发者,了解 DWARF 格式的重要目的一般是为了开发一些工具,完成程序运行时到源码之间的映射。例如:
- atos: 找到程序运行时的内存地址对应的文件名,行号及所在函数名
- atosl: atos 的 Linux 实现, 由 facebook 开发
另外, 有一些领域可以预见到DWARF信息会提供一些帮助,例如: 静态代码扫描(static code analysis), 二进制代码性能分析(performance analysis of binary)
下面给出了一些开发相关的库:
- libdwarf, 一个 C 语言库,用来解析 DWARF 格式,被适用于 atosl 中
- libdwarf/libdwarf2.1.pdf, libdwarf 的使用文档
- dwarfdump, 一个程序, 用于探索DWARF内容
- golang debug/dwarf, Golang 标准库 debug/dwarf 提供了简单的 DWARF 解析能力
- LLVM-DWARF: LLVM 项目中与 DWARF 相关的部分,可了解编译器生成DWARF信息的细节
作为一个Golang 使用 debug/dwarf 的例子, ParseFile 打印了 address 对应的文件名及行号
package dwarfexample
import (
"debug/macho"
"debug/dwarf"
"log"
"github.com/go-errors/errors"
)
func ParseFile(path string, address int64) (err error) {
var f *macho.FatFile
if f, err = macho.OpenFat(path); err != nil {
return errors.New("open file error: " + err.Error())
}
var d *dwarf.Data
if d, err = f.Arches[1].DWARF(); err != nil {
return
}
r := d.Reader()
var entry *dwarf.Entry
if entry, err = r.SeekPC(address); err != nil {
log.Print("Not Found ...")
return
} else {
log.Print("Found ...")
}
log.Printf("tag: %+v, lowpc: %+v", entry.Tag, entry.Val(dwarf.AttrLowpc))
var lineReader *dwarf.LineReader
if lineReader, err = d.LineReader(entry); err != nil {
return
}
var line dwarf.LineEntry
if err = lineReader.SeekPC(0x1005AC550, &line); err != nil {
return
}
log.Printf("line %+v:%+v", line.File.Name, line.Line)
return
}
DWARF 资源
下面是DWARF相关的资源链接:
- DWARF4 标准
- 调试器原理介绍
- ptrace 系统调用 Man 文档、ptrace 系统调用(OS X) Man 文档
- 地址空间布局随机化(ASLR)机制的分析与绕过
- DWARF 标准委员会主页
- libdwarf 库主页
- LLVM DWARF相关模块源码
- DWARF 很好的入门文章
- IBM 开发者社区关于DWARF的一篇文章
- X86汇编调用框架浅析与CFI简介
- 使用LLVM进行源码级别调试
- 如何使用ptrace的两篇文章: Playing with ptrace, Part I、Playing with ptrace, Part II
- 关于DWARF各个Section使用存储的文章, 对比了GCC和LLVM: A comparison of the DWARF debugging information produced by LLVM and GCC
- ptrace 在OS X下的替代方案
上一篇: 升级体验:CANape 19真的很赞!
下一篇: Python中的a2l解析详解
推荐阅读
-
如何选择合适的配色方案?满足你的需要,也能让你亲自参与设计
-
你可能也需要试试DWARF
-
HHKB BT 蓝牙键盘:装逼必备利器?" 使用HHKB BT 蓝牙键盘的心得体会 在互联网的世界里,越来越多的人都追求个性化和独特性,无论是穿着打扮,还是使用的电子产品,都能看到人们的创意和想法。最近我在网上看到了一款备受推崇的机械键盘——HHKB BT 蓝牙键盘。 HHKB BT 蓝牙键盘是一款由日本知名键盘制造商 Filco 生产的产品,被誉为程序员的信仰。其最大的特点就是采用了静电容轴,按键更加灵敏且耐用。此外,HHKB BT 蓝牙键盘还具有非常紧凑的设计,仅60个键位,非常适合长时间工作或编程的人士使用。 然而,当我收到 HHKB BT 蓝牙键盘并开始使用时,我发现了一些问题。首先,数字键整体向右移动了一位,这对于习惯使用数字键的人来说是一种困扰。其次,HHKB BT 蓝牙键盘的价格较高,并且只能通过代购购买,售后服务也不完善。 尽管存在这些问题,我还是对 HHKB BT 蓝牙键盘产生了浓厚的兴趣。我尝试将数字键重新排列,使其更适合我的使用习惯,同时我也开始学习如何更有效地使用 HHKB BT 蓝牙键盘进行编程。 总的来说,HHKB BT 蓝牙键盘是一款非常专业且高效的机械键盘,如果你是一位需要长时间使用键盘的开发者或者程序员,那么它绝对值得你考虑。当然,高昂的价格和售后服务的问题也需要你权衡一下是否值得投资。
-
实用工具推荐:Efficiency & Impact矩阵,你可能需要的决策辅助工具
-
SSM三大框架基础面试题-一、Spring篇 什么是Spring框架? Spring是一种轻量级框架,提高开发人员的开发效率以及系统的可维护性。 我们一般说的Spring框架就是Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具、消息和测试模块。比如Core Container中的Core组件是Spring所有组件的核心,Beans组件和Context组件是实现IOC和DI的基础,AOP组件用来实现面向切面编程。 Spring的6个特征: 核心技术:依赖注入(DI),AOP,事件(Events),资源,i18n,验证,数据绑定,类型转换,SpEL。 测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。 数据访问:事务,DAO支持,JDBC,ORM,编组XML。 Web支持:Spring MVC和Spring WebFlux Web框架。 集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。 语言:Kotlin,Groovy,动态语言。 列举一些重要的Spring模块? Spring Core:核心,可以说Spring其他所有的功能都依赖于该类库。主要提供IOC和DI功能。 Spring Aspects:该模块为与AspectJ的集成提供支持。 Spring AOP:提供面向切面的编程实现。 Spring JDBC:Java数据库连接。 Spring JMS:Java消息服务。 Spring ORM:用于支持Hibernate等ORM工具。 Spring Web:为创建Web应用程序提供支持。 Spring Test:提供了对JUnit和TestNG测试的支持。 谈谈自己对于Spring IOC和AOP的理解 IOC(Inversion Of Controll,控制反转)是一种设计思想: 在程序中手动创建对象的控制权,交由给Spring框架来管理。IOC在其他语言中也有应用,并非Spring特有。IOC容器实际上就是一个Map(key, value),Map中存放的是各种对象。 将对象之间的相互依赖关系交给IOC容器来管理,并由IOC容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IOC容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。在实际项目中一个Service类可能由几百甚至上千个类作为它的底层,假如我们需要实例化这个Service,可能要每次都搞清楚这个Service所有底层类的构造函数,这可能会把人逼疯。如果利用IOC的话,你只需要配置好,然后在需要的地方引用就行了,大大增加了项目的可维护性且降低了开发难度。 Spring中的bean的作用域有哪些? 1.singleton:该bean实例为单例 2.prototype:每次请求都会创建一个新的bean实例(多例)。 3.request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。 4.session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。 5.global-session:全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个Portlet都有不同的会话。 Spring中的单例bean的线程安全问题了解吗? 概念用于理解:大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例bean存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。 有两种常见的解决方案(用于回答的点): 1.在bean对象中尽量避免定义可变的成员变量(不太现实)。 2.在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal(线程本地化对象)中(推荐的一种方式)。 ThreadLocal解决多线程变量共享问题(参考博客):https://segmentfault.com/a/1190000009236777 Spring中Bean的生命周期: 1.Bean容器找到配置文件中Spring Bean的定义。 2.Bean容器利用Java Reflection API创建一个Bean的实例。 3.如果涉及到一些属性值,利用set方法设置一些属性值。 4.如果Bean实现了BeanNameAware接口,调用setBeanName方法,传入Bean的名字。 5.如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader方法,传入ClassLoader对象的实例。 6.如果Bean实现了BeanFactoryAware接口,调用setBeanClassFacotory方法,传入ClassLoader对象的实例。 7.与上面的类似,如果实现了其他*Aware接口,就调用相应的方法。 8.如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执postProcessBeforeInitialization方法。 9.如果Bean实现了InitializingBean接口,执行afeterPropertiesSet方法。 10.如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。 11.如果有和加载这个Bean的Spring容器相关的BeanPostProcess对象,执行postProcessAfterInitialization方法。 12.当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy方法。 13.当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。 Spring框架中用到了哪些设计模式? 1.工厂设计模式:Spring使用工厂模式通过BeanFactory和ApplicationContext创建bean对象。 2.代理设计模式:Spring AOP功能的实现。 3.单例设计模式:Spring中的bean默认都是单例的。 4.模板方法模式:Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。 5.包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。 6.观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。 7.适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller。 还有很多。。。。。。。 @Component和@Bean的区别是什么 1.作用对象不同。@Component注解作用于类,而@Bean注解作用于方法。 2.@Component注解通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用@ComponentScan注解定义要扫描的路径)。@Bean注解通常是在标有该注解的方法中定义产生这个bean,告诉Spring这是某个类的实例,当我需要用它的时候还给我。 3.@Bean注解比@Component注解的自定义性更强,而且很多地方只能通过@Bean注解来注册bean。比如当引用第三方库的类需要装配到Spring容器的时候,就只能通过@Bean注解来实现。 @Configuration public class AppConfig { @Bean public TransferService transferService { return new TransferServiceImpl; } } <beans> <bean id="transferService" class="com.kk.TransferServiceImpl"/> </beans> @Bean public OneService getService(status) { case (status) { when 1: return new serviceImpl1; when 2: return new serviceImpl2; when 3: return new serviceImpl3; } } 将一个类声明为Spring的bean的注解有哪些? 声明bean的注解: @Component 组件,没有明确的角色 @Service 在业务逻辑层使用(service层) @Repository 在数据访问层使用(dao层) @Controller 在展现层使用,控制器的声明 注入bean的注解: @Autowired:由Spring提供 @Inject:由JSR-330提供 @Resource:由JSR-250提供 *扩:JSR 是 java 规范标准 Spring事务管理的方式有几种? 1.编程式事务:在代码中硬编码(不推荐使用)。 2.声明式事务:在配置文件中配置(推荐使用),分为基于XML的声明式事务和基于注解的声明式事务。 Spring事务中的隔离级别有哪几种? 在TransactionDefinition接口中定义了五个表示隔离级别的常量:ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,Mysql默认采用的REPEATABLE_READ隔离级别;Oracle默认采用的READ_COMMITTED隔离级别。ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 Spring事务中有哪几种事务传播行为? 在TransactionDefinition接口中定义了八个表示事务传播行为的常量。 支持当前事务的情况:PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)。 不支持当前事务的情况:PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。 其他情况:PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。 二、SpringMVC篇 什么是Spring MVC ?简单介绍下你对springMVC的理解? Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。 Spring MVC的工作原理了解嘛? image.png Springmvc的优点: (1)可以支持各种视图技术,而不仅仅局限于JSP; (2)与Spring框架集成(如IoC容器、AOP等); (3)清晰的角色分配:前端控制器(dispatcherServlet) , 请求到处理器映射(handlerMapping), 处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。 (4) 支持各种请求资源的映射策略。 Spring MVC的主要组件? (1)前端控制器 DispatcherServlet(不需要程序员开发) 作用:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。 (2)处理器映射器HandlerMapping(不需要程序员开发) 作用:根据请求的URL来查找Handler (3)处理器适配器HandlerAdapter 注意:在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以正确的去执行Handler。 (4)处理器Handler(需要程序员开发) (5)视图解析器 ViewResolver(不需要程序员开发) 作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view) (6)视图View(需要程序员开发jsp) View是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf等等) springMVC和struts2的区别有哪些? (1)springmvc的入口是一个servlet即前端控制器(DispatchServlet),而struts2入口是一个filter过虑器(StrutsPrepareAndExecuteFilter)。 (2)springmvc是基于方法开发(一个url对应一个方法),请求参数传递到方法的形参,可以设计为单例或多例(建议单例),struts2是基于类开发,传递参数是通过类的属性,只能设计为多例。 (3)Struts采用值栈存储请求和响应的数据,通过OGNL存取数据,springmvc通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用jstl。 SpringMVC怎么样设定重定向和转发的? (1)转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4" (2)重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com" SpringMvc怎么和AJAX相互调用的? 通过Jackson框架就可以把Java里面的对象直接转化成Js可以识别的Json对象。具体步骤如下 : (1)加入Jackson.jar (2)在配置文件中配置json的映射 (3)在接受Ajax方法里面可以直接返回Object,List等,但方法前面要加上@ResponseBody注解。 如何解决POST请求中文乱码问题,GET的又如何处理呢? (1)解决post请求乱码问题: 在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8; <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> (2)get请求中文参数出现乱码解决方法有两个: ①修改tomcat配置文件添加编码与工程编码一致,如下: <ConnectorURIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/> ②另外一种方法对参数进行重新编码: String userName = new String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8") ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。 Spring MVC的异常处理 ? 统一异常处理: Spring MVC处理异常有3种方式: (1)使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver; (2)实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器; (3)使用@ExceptionHandler注解实现异常处理; 统一异常处理的博客:https://blog.csdn.net/ctwy291314/article/details/81983103 SpringMVC的控制器是不是单例模式,如果是,有什么问题,怎么解决? 是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写成员变量。(此题目类似于上面Spring 中 第5题 有两种解决方案) SpringMVC常用的注解有哪些? @RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。 @RequestBody:注解实现接收http请求的json数据,将json转换为java对象。 @ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。 SpingMvc中的控制器的注解一般用那个,有没有别的注解可以替代? 一般用@Controller注解,也可以使用@RestController,@RestController注解相当于@ResponseBody + @Controller,表示是表现层,除此之外,一般不用别的注解代替。 如果在拦截请求中,我想拦截get方式提交的方法,怎么配置? 可以在@RequestMapping注解里面加上method=RequestMethod.GET。 怎样在方法里面得到Request,或者Session? 直接在方法的形参中声明request,SpringMVC就自动把request对象传入。 如果想在拦截的方法里面得到从前台传入的参数,怎么得到? 直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样。 如果前台有很多个参数传入,并且这些参数都是一个对象的,那么怎么样快速得到这个对象? 直接在方法中声明这个对象,SpringMVC就自动会把属性赋值到这个对象里面。 SpringMVC中函数的返回值是什么? 返回值可以有很多类型,有String, ModelAndView。ModelAndView类把视图和数据都合并的一起的。 SpringMVC用什么对象从后台向前台传递数据的? 通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以拿到数据。 怎么样把ModelMap里面的数据放入Session里面? 可以在类上面加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key。 SpringMvc里面拦截器是怎么写的: 有两种写法,一种是实现HandlerInterceptor接口,另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑;然后在SpringMvc的配置文件中配置拦截器即可: <!-- 配置SpringMvc的拦截器 --> <mvc:interceptors> <!-- 配置一个拦截器的Bean就可以了 默认是对所有请求都拦截 --> <bean id="myInterceptor" class="com.zwp.action.MyHandlerInterceptor"></bean> <!-- 只针对部分请求拦截 --> <mvc:interceptor> <mvc:mapping path="/modelMap.do" /> <bean class="com.zwp.action.MyHandlerInterceptorAdapter" /> </mvc:interceptor> </mvc:interceptors> 注解原理: 注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池 三、Mybatis篇 什么是MyBatis? MyBatis是一个可以自定义SQL、存储过程和高级映射的持久层框架。 讲下MyBatis的缓存 MyBatis的缓存分为一级缓存和二级缓存,一级缓存放在session里面,默认就有, 二级缓存放在它的命名空间里,默认是不打开的,使用二级缓存属性类需要实现Serializable序列化接口, 可在它的映射文件中配置<cache/> Mybatis是如何进行分页的?分页插件的原理是什么? 1)Mybatis使用RowBounds对象进行分页,也可以直接编写sql实现分页,也可以使用Mybatis的分页插件。 2)分页插件的原理:实现Mybatis提供的接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql。 举例:select * from student,拦截sql后重写为:select t.* from (select * from student)t limit 0,10 简述Mybatis的插件运行原理,以及如何编写一个插件? 1)Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、 Executor这4种接口的插件,Mybatis通过动态代理, 为需要拦截的接口生成代理对象以实现接口方法拦截功能, 每当执行这4种接口对象的方法时,就会进入拦截方法, 具体就是InvocationHandler的invoke方法,当然, 只会拦截那些你指定需要拦截的方法。 2)实现Mybatis的Interceptor接口并复写intercept方法, 然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可, 记住,别忘了在配置文件中配置你编写的插件。 Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不? 1)Mybatis动态sql可以让我们在Xml映射文件内, 以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能。 2)Mybatis提供了9种动态sql标签:trim|where|set|foreach|if|choose|when|otherwise|bind。 3)其执行原理为,使用OGNL从sql参数对象中计算表达式的值, 根据表达式的值动态拼接sql,以此来完成动态sql的功能。 #{}和${}的区别是什么? 1)#{}是预编译处理,${}是字符串替换。 2)Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值(有效的防止SQL注入); 3)Mybatis在处理${}时,就是把${}替换成变量的值。 为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里? Hibernate属于全自动ORM映射工具, 使用Hibernate查询关联对象或者关联集合对象时, 可以根据对象关系模型直接获取,所以它是全自动的。 而Mybatis在查询关联对象或关联集合对象时, 需要手动编写sql来完成,所以,称之为半自动ORM映射工具。 Mybatis是否支持延迟加载?如果支持,它的实现原理是什么? 1)Mybatis仅支持association关联对象和collection关联集合对象的延迟加载, association指的就是一对一,collection指的就是一对多查询。 在Mybatis配置文件中, 可以配置是否启用延迟加载lazyLoadingEnabled=true|false。 2)它的原理是,使用CGLIB创建目标对象的代理对象, 当调用目标方法时,进入拦截器方法, 比如调用a.getB.getName, 拦截器invoke方法发现a.getB是null值, 那么就会单独发送事先保存好的查询关联B对象的sql, 把B查询上来,然后调用a.setB(b), 于是a的对象b属性就有值了, 接着完成a.getB.getName方法的调用。 这就是延迟加载的基本原理。 MyBatis与Hibernate有哪些不同? 1)Mybatis和hibernate不同,它不完全是一个ORM框架, 因为MyBatis需要程序员自己编写Sql语句, 不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句, 并将java对象和sql语句映射生成最终执行的sql, 最后将sql执行的结果再映射生成java对象。 2)Mybatis学习门槛低,简单易学,程序员直接编写原生态sql, 可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发, 例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁, 一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性, 如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。 3)Hibernate对象/关系映射能力强,数据库无关性好, 对于关系模型要求高的软件(例如需求固定的定制化软件) 如果用hibernate开发可以节省很多代码,提高效率。 但是Hibernate的缺点是学习门槛高,要精通门槛更高, 而且怎么设计O/R映射,在性能和对象模型之间如何权衡, 以及怎样用好Hibernate需要具有很强的经验和能力才行。 总之,按照用户的需求在有限的资源环境下只要能做出维护性、 扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。 MyBatis的好处是什么? 1)MyBatis把sql语句从Java源程序中独立出来,放在单独的XML文件中编写, 给程序的维护带来了很大便利。 2)MyBatis封装了底层JDBC API的调用细节,并能自动将结果集转换成Java Bean对象, 大大简化了Java数据库编程的重复工作。 3)因为MyBatis需要程序员自己去编写sql语句, 程序员可以结合数据库自身的特点灵活控制sql语句, 因此能够实现比Hibernate等全自动orm框架更高的查询效率,能够完成复杂查询。 简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系? Mybatis将所有Xml配置信息都封装到All-In-One重量级对象Configuration内部。 在Xml映射文件中,<parameterMap>标签会被解析为ParameterMap对象, 其每个子元素会被解析为ParameterMapping对象。 <resultMap>标签会被解析为ResultMap对象, 其每个子元素会被解析为ResultMapping对象。 每一个<select>、<insert>、<update>、<delete> 标签均会被解析为MappedStatement对象, 标签内的sql会被解析为BoundSql对象。 什么是MyBatis的接口绑定,有什么好处? 接口映射就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定, 我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置. 接口绑定有几种实现方式,分别是怎么实现的? 接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加 上@Select@Update等注解里面包含Sql语句来绑定, 另外一种就是通过xml里面写SQL来绑定,在这种情况下, 要指定xml映射文件里面的namespace必须为接口的全路径名. 什么情况下用注解绑定,什么情况下用xml绑定? 当Sql语句比较简单时候,用注解绑定;当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多 MyBatis实现一对一有几种方式?具体怎么操作的? 有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap里面配置association节点配置一对一的类就可以完成; 嵌套查询是先查一个表,根据这个表里面的结果的外键id, 去再另外一个表里面查询数据,也是通过association配置, 但另外一个表的查询通过select属性配置。 Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别? 能,Mybatis不仅可以执行一对一、一对多的关联查询, 还可以执行多对一,多对多的关联查询,多对一查询, 其实就是一对一查询,只需要把selectOne修改为selectList即可; 多对多查询,其实就是一对多查询,只需要把selectOne修改为selectList即可。 关联对象查询,有两种实现方式,一种是单独发送一个sql去查询关联对象, 赋给主对象,然后返回主对象。另一种是使用嵌套查询,嵌套查询的含义为使用join查询, 一部分列是A对象的属性值,另外一部分列是关联对象B的属性值, 好处是只发一个sql查询,就可以把主对象和其关联对象查出来。 MyBatis里面的动态Sql是怎么设定的?用什么语法? MyBatis里面的动态Sql一般是通过if节点来实现,通过OGNL语法来实现, 但是如果要写的完整,必须配合where,trim节点,where节点是判断包含节点有 内容就插入where,否则不插入,trim节点是用来判断如果动态语句是以and 或or 开始,那么会自动把这个and或者or取掉。 Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式? 第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系。 第二种是使用sql列的别名功能,将列别名书写为对象属性名, 比如T_NAME AS NAME,对象属性名一般是name,小写, 但是列名不区分大小写,Mybatis会忽略列名大小写,
-
现在是 2024 年,还在学习 pytest - 夹具问题整理 - 跟随博主提问:是在电脑本地终端安装,还是在 pythoncharm 终端安装?------ 就在 pythoncharm 终端安装吧 ** 注:在公司安装不行,换豆豆源也不行,连接手机热点试试,可能是公司网络阻塞。 venvenv变红了且安装不上pytest 然后你会发现 改一下选择现有,就解决了,还有路径中最好不要有中文7.16-学习demo的过程发现:为何一定需要@pytest.fixture才能正常传递参数
-
异步编程RxJava-介绍-前言 前段时间写了一篇对协程的一些理解,里面提到了不管是协程还是callback,本质上其实提供的是一种异步无阻塞的编程模式;并且介绍了java中对异步无阻赛这种编程模式的支持,主要提到了Future和CompletableFuture;之后有同学在下面留言提到了RxJava,刚好最近在看微服务设计这本书,里面提到了响应式扩展(Reactive extensions,Rx),而RxJava是Rx在JVM上的实现,所有打算对RxJava进一步了解。 RxJava简介 RxJava的官网地址:https://github.com/ReactiveX/RxJava, 其中对RxJava进行了一句话描述:RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM. 大意就是:一个在Java VM上使用可观测的序列来组成异步的、基于事件的程序的库。 更详细的说明在Netflix技术博客的一篇文章中描述了RxJava的主要特点: 1.易于并发从而更好的利用服务器的能力。 2.易于有条件的异步执行。 3.一种更好的方式来避免回调地狱。 4.一种响应式方法。 与CompletableFuture对比 之前提到CompletableFuture真正的实现了异步的编程模式,一个比较常见的使用场景: CompletableFuture<Integer> future = CompletableFuture.supplyAsync(耗时函数); Future<Integer> f = future.whenComplete((v, e) -> { System.out.println(v); System.out.println(e); }); System.out.println("other..."); 下面用一个简单的例子来看一下RxJava是如何实现异步的编程模式: Observable<Long> observable = Observable.just(1, 2) .subscribeOn(Schedulers.io).map(new Func1<Integer, Long> { @Override public Long call(Integer t) { try { Thread.sleep(1000); //耗时的操作 } catch (InterruptedException e) { e.printStackTrace; } return (long) (t * 2); } }); observable.subscribe(new Subscriber<Long> { @Override public void onCompleted { System.out.println("onCompleted"); } @Override public void onError(Throwable e) { System.out.println("error" + e); } @Override public void onNext(Long result) { System.out.println("result = " + result); } }); System.out.println("other..."); Func1中以异步的方式执行了一个耗时的操作,Subscriber(观察者)被订阅到Observable(被观察者)中,当耗时操作执行完会回调Subscriber中的onNext方法。 其中的异步方式是在subscribeOn(Schedulers.io)中指定的,Schedulers.io可以理解为每次执行耗时操作都启动一个新的线程。 结构上其实和CompletableFuture很像,都是异步的执行一个耗时的操作,然后在有结果的时候主动告诉我结果。那我们还需要RxJava干嘛,不知道你有没有注意,上面的例子中其实提供2条数据流[1,2],并且处理完任何一个都会主动告诉我,当然这只是它其中的一项功能,RxJava还有很多好用的功能,在下面的内容会进行介绍。 异步观察者模式 上面这段代码有没有发现特别像设计模式中的:观察者模式;首先提供一个被观察者Observable,然后把观察者Subscriber添加到了被观察者列表中; RxJava中一共提供了四种角色:Observable、Observer、Subscriber、Subjects Observables和Subjects是两个被观察者,Observers和Subscribers是观察者; 当然我们也可以查看一下源码,看一下jdk中的Observer和RxJava的Observer jdk中的Observer: public interface Observer { void update(Observable o, Object arg); } RxJava的Observer: public interface Observer<T> { void onCompleted; void onError(Throwable e); void onNext(T t); } 同时可以发现Subscriber是implements Observer的: public abstract class Subscriber<T> implements Observer<T>, Subscription 可以发现RxJava中在Observer中引入了2个新的方法:onCompleted和onError onCompleted:即通知观察者Observable没有更多的数据,事件队列完结 onError:在事件处理过程中出异常时,onError会被触发,同时队列自动终止,不允许再有事件发出。 正是因为RxJava提供了同步和异步两种方式进行事件的处理,个人觉得异步的方式更能体现RxJava的价值,所以这里给他命名为异步观察者模式。 好了,下面正式介绍RxJava的那些灵活的操作符,这里仅仅是简单的介绍和简单的实例,具体用在什么场景下,会在以后的文章中介绍 Maven引入
-
将来你可能再也不需要驾照就能开车了,这种可能性是怎么回事?
-
大聪明教你学Java|Mybatis的一级缓存和二级缓存--🍊作者简介:不愿过河东,一个来自二线城市的程序员,致力于用 "猥琐 "的方法解决琐碎的问题,让复杂的问题变得简单易懂。支持作者:喜欢👍,关注💖,留言💌~! 前言。 在计算机世界中,缓存无处不在;操作系统有操作系统缓存,数据库会有数据库缓存,我们还可以利用中间件(如 Redis)来充当缓存。MyBatis 作为一个优秀的 ORM 框架,也用于缓存,所以今天我们就来谈谈 Mybatis 的一级缓存和二级缓存。 Mybatis 一级缓存 首先,我们来看一张图片👇。 我们在开发项目的过程中,如果打开Mybatis的SQL语句打印,经常会看到这样一句话:创建一个新的 SqlSession,其实这就是我们常说的 Mybatis 一级缓存。 Mybatis 的一级缓存也就是在执行一次 SQL 查询或 SQL 更新后,这条 SQL 语句并不会消失,而是被 MyBatis 缓存起来,当再次执行同样的 SQL 语句时,就会直接从缓存中提取出来,而不用再次执行 SQL 命令。第一级缓存也称为 SqlSession 级缓存。对数据库进行操作时,需要构建一个 SqlSession 对象,其中有一个用于存储缓存数据的数据结构(HashMap)。对象中有一个用于存储缓存数据的数据结构(HashMap)。不同 SqlSession 之间的缓存数据区域(HashMap)互不影响。 在我们的应用系统运行过程中,我们可能会在一个数据库会话中,执行多条查询条件相同的 SQL 语句,那么对于这种情况,你来进行设计,那么你会如何考虑呢?没错,就是加入缓存,MyBatis也是这么来处理的,如果是同一条SQL语句,会优先打入一级缓存,避免直接查询数据库,给数据库造成压力,提高性能。具体实现过程如下图所示👇 SqlSession 是一个接口,提供了一些 CRUD 方法,SqlSession 的默认实现类是 DefaultSqlSession,DefaultSqlSession 类持有 Executor 接口对象,Executor 的默认实现是 BaseExecutor 对象,每个 BaseExecutor 对象都有一个 PerpetualCache 缓存,即上图中的本地缓存。当用户发起查询时,MyBatis 会根据当前执行的语句生成一个 MappedStatement,并在本地缓存中进行查询,如果缓存被命中,查询结果会直接返回给用户;如果缓存未被命中,查询结果会直接返回给用户。如果缓存未命中,则查询数据库,将结果写入本地缓存,最后将结果返回给用户。这时候可能有小伙伴要说了:我还在控制台中看到 "关闭非事务性 SqlSession "这句话,说明我每次创建一个 SqlSession 到 SqlSession 结束都是关闭的,那么我的缓存还是有毛线用!😥 事情肯定不是我们想的那样,让我们继续👇。 🍊 getSqlSession 源代码
-
重启系统 (4 级 2021-03 T) 解决方案-主题 主题描述 小明正在帮助管理一个处理数据的计算系统。有 N 个任务需要处理,它们需要按顺序完成,即每个已完成任务的数量必须大于前一个已完成任务的数量,并且单个任务不能被分解。计算系统正在运行一个奇怪的保护程序,该程序将系统可处理的数据量限制为不超过最后完成的任务。重启系统后,系统立即恢复到最高性能(系统一开始就具有最高性能,大于任何任务的处理数据量)。小明有权限重启系统一次(也可以不重启),你能帮他计算出他能完成的最大任务数吗? 输入 第一行:N (2 <= N <= 1000) 待处理任务数 第二行:N 个整数,每个任务的数据量 输出 输出只有一行,其中只包含一个整数,表示可完成的最大任务数。 输入副本示例 10 1 5 4 3 2 10 9 8 7 6 输出副本示例 9 提示 来源 理念