title: uboot2012(一)分析重定位

date: 2019/02/23 21:53:21

toc: true

引入

关于移植,搜索关键英文词语 portting

移植简单的介绍在readme中,手册是它的使用帮助

代码仓库地址 02-uboot重定位加入自己的代码

环境配置

这里使用编译工具arm-linux-gcc-4.3.2.tar,具体安装参考更换gcc工具链.md

编译体验

 make smdk2410_config
make

入口查找

我们可以从顶层Makefile开始分析,也可以直接看到编译结果,查看最后的链接过程如下,搜索arm-linux-

arm-linux-ld  -pie -T u-boot.lds -Bstatic -Ttext 0x0 $UNDEF_SYM arch/arm/cpu/arm920t/start.o --start-group api/libapi.o

可以看到这里指定了链接脚本以及代码段的地址在-Ttext 0,第一个文件是arch/arm/cpu/arm920t/start.o

代码分析

简单的流程如下:

  1. set the cpu to SVC32 mode
  2. close watchdog
  3. mask all IRQs by setting all bits in the INTMR
  4. FCLK:HCLK:PCLK = 1:2:4
  5. cpu_init_crit
    1. flush v4 I/D caches
    2. disable MMU stuff and caches
    3. lowlevel_init
      1. memory control configuration [set sdram]
    4. set sp [Set stackpointer in internal RAM]
    5. board_init_f
      1. init_sequence

        1. board_early_init_f
        2. set clock
        3. gpio
      2. ----....---
    6. relocate_code
    7. copy_loop 复制代码
    8. fixloop 修改全局变量等数据段
    9. clear_bss

board_init_f

这里有一个关键的变量gd,可以看到文件头上面有个宏DECLARE_GLOBAL_DATA_PTR,定义如下

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

也就是定义gd为寄存器变量r8,可以看到编译输出有这么一句,表示编译器不使用r8寄存器

-fno-common -ffixed-r8

pie

新版本的uboot是在初始化完执行完board_init_f后进行重定位代码,但是代码本身的链接脚本就是在0地址的,那么它为什么需要再重定位代码?

我们可以看到链接命令输出如下

arm-linux-ld  -pie -T u-boot.lds -Bstatic -Ttext 0x0 $UNDEF_SYM arch/arm/cpu/arm920t/start.o --start-group api/libapi.o

搜索下pie相关的内容,就是说创建位置无关可执行程序

$ arm-linux-ld --help | grep "pie"
-pie, --pic-executable Create a position independent executable

内存分布分析

我们从进入c函数的地方开始分析,前面的就不看了,c函数跳转首先需要这是sp

SP设置

到设置堆栈的代码为入口分析

/* Set stackpointer in internal RAM to call board_init_f */
call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r0,=0x00000000
bl board_init_f

这里设置sp=CONFIG_SYS_INIT_SP_ADDR,我们可以查看反汇编得到实际的值是0x30000f80

00000098 <call_board_init_f>:
98: e59fd3d8 ldr sp, [pc, #984] ; 478 <fiq+0x58>
9c: e3cdd007 bic sp, sp, #7 ; 0x7
a0: e3a00000 mov r0, #0 ; 0x0
a4: eb0007f1 bl 2070 <board_init_f> 478: 30000f80 .word 0x30000f80

接下来仔细看下代码,这里有个宏DEFINE,目的是将后面的参数1作为一个宏传递给汇编文件,值是第二个参数,这里的值就是global_data也就是gd_t向上16对齐

#define PHYS_SDRAM_1		0x30000000 /* SDRAM Bank #1 */
#define PHYS_SDRAM_1_SIZE 0x04000000 /* 64 MB */
#define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM_1
#define CONFIG_SYS_INIT_SP_ADDR (CONFIG_SYS_SDRAM_BASE + 0x1000 - \
GENERATED_GBL_DATA_SIZE) lib\asm-offsets.c
DEFINE(GENERATED_GBL_DATA_SIZE,
(sizeof(struct global_data) + 15) & ~15); typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /* serial_init() was called */ unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /* Checksum of Environment valid? */
unsigned long fb_base; /* base address of frame buffer */ #ifdef CONFIG_ARM
/* "static data" needed by most of timer.c on ARM platforms */
unsigned long timer_rate_hz;
unsigned long tbl;
unsigned long tbu;
unsigned long long timer_reset_value;
unsigned long lastinc;
#endif
unsigned long relocaddr; /* Start address of U-Boot in RAM */
phys_size_t ram_size; /* RAM size */
unsigned long mon_len; /* monitor len */
unsigned long irq_sp; /* irq stack pointer */
unsigned long start_addr_sp; /* start_addr_stackpointer */
unsigned long reloc_off;
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))
unsigned long tlb_addr;
#endif
const void *fdt_blob; /* Our device tree, NULL if none */
void **jt; /* jump table */
char env_buf[32]; /* buffer for getenv() before reloc. */
} gd_t;

刚开始的时候,没有考虑到CONFIG_ARM,计算出来的值与实际对不上,后来仔细搜索发现是定义了的

# grep  -nR "CONFIG_ARM" ./
./arch/arm/config.mk:34:PLATFORM_CPPFLAGS += -DCONFIG_ARM -D__ARM__
./include/autoconf.mk:126:CONFIG_ARM=y

这里理论计算的就是22*4+32=120===+15&-15=128

最终sp=0x30000000+0x1000-128=0x30000F80与汇编结果是一致的

接着我们仔细看下这个来自内核的宏DEFINE,它是嵌入汇编,实际上他是编译不了的,因为没有->指令,实际上,这个asm-offsets.c文件根本不是用来运行的,只是在编译的时候,用它生成一个asm-offsets.s文件,然后Kbuild会处理这个asm-offsets.s文件,生成asm-offsets.h文件。这个头文件最终被汇编文件引用,其中定义的变量最终得到应用。

如果要在自己的内核模块中使用这些变量,引用这个头文件即可#include <asm/asm-offsets.h>. 摘自内核黑科技之DEFINE宏

include\linux\kbuild.h
#define DEFINE(sym, val) \
asm volatile("\n->" #sym " %0 " #val : : "i" (val))

后来在编译结果中搜索CONFIG_SYS_INIT_SP_ADDR

./include/generated/generic-asm-offsets.h:10:#define GENERATED_GBL_DATA_SIZE (128) /* (sizeof(struct global_data) + 15) & ~15 */

其实这个是我看了说明之后才去搜索的,这个DEFINE就是根据lib/asm-offsets.c生成asm-offsets.h

$ cat asm-offsets.h
#ifndef DO_DEPS_ONLY #include <generated/generic-asm-offsets.h>
/* #include <generated/asm-offsets.h> */ #endif

最终的宏也就在generated/generic-asm-offsets.h中了

#define GENERATED_GBL_DATA_SIZE (128) /* (sizeof(struct global_data) + 15) & ~15 */
#define GENERATED_BD_INFO_SIZE (48) /* (sizeof(struct bd_info) + 15) & ~15 */

接下来就是初始化以及内存分布设置

board_init_f

简单的代码解释如下

call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, #7 /* 8-byte alignment for ABI compliance 清除低3位 */
ldr r0,=0x00000000
bl board_init_f board_init_f
/*这里指的就是栈顶 调用之前是 ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) bic sp, sp, #7 */
gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07); gd->mon_len = _bss_end_ofs; /**bss_end-start 就是整个程序的大小/ /*一些初始化操作*/
*init_sequence() addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size; //这个ram_size在dram_init 初始化为64M,CONFIG_SYS_SDRAM_BASE 也就是sdram基地址
//addr 就是ram最高地址 0x04000000+0x3000,0000 /* reserve TLB table */ //保留4kb给tlb后向下64kb对齐,也就是消除0xffff 0x3400,0000-0x4000=0x33ffc,0000
addr -= (4096 * 4); // 对齐后就是 0x33ff,0000
/* round down to next 64 kB limit */
addr &= ~(0x10000 - 1);
gd->tlb_addr = addr; /* //代码段,gd->mon_len=_bss_end_ofs
* reserve memory for U-Boot code, data & bss
* round down to next 4 kB limit
*/
addr -= gd->mon_len;
addr &= ~(4096 - 1); /*
* reserve memory for malloc() arena
*/
addr_sp = addr - TOTAL_MALLOC_LEN; /*
* (permanently) allocate a Board Info struct
* and a permanent copy of the "global" data
*/
addr_sp -= sizeof (bd_t);
bd = (bd_t *) addr_sp;
gd->bd = bd; addr_sp -= sizeof (gd_t);
id = (gd_t *) addr_sp; /* leave 3 words for abort-stack */
addr_sp -= 12; /* 8-byte alignment for ABI compliance */
addr_sp &= ~0x07; gd->relocaddr = addr;
gd->start_addr_sp = addr_sp;
gd->reloc_off = addr - _TEXT_BASE;
memcpy(id, (void *)gd, sizeof(gd_t)); /*这里最后进行重定位代码 */
relocate_code(addr_sp, id, addr); relocate_code:
mov r4, r0 /* save addr_sp */
mov r5, r1 /* save addr of gd */
mov r6, r2 /* save addr of destination */ int dram_init(void)
{
/* dram_init must store complete ramsize in gd->ram_size */
gd->ram_size = PHYS_SDRAM_1_SIZE; //64M
return 0;
}

最后的分配内存如下

重定位

uboot的链接地址是0,那么它刚开始在nor上运行是能够读取指令运行的,但是他的变量怎么办呢?

比如我有一个变量在0x100,需要修改,nor上的数据并不能像内存一样修改?那么怎么解决呢?

这里我们假设想要在sdram上运行,那么我们搬运到0x3200,0000上去,那么我们不仅需要搬运代码,还要修改代码也就是说将变量0x100变为0x3200,0100

那么我们怎么知道旧变量的地址0x100,这里就是在链接的时候加入pie选项,会有新的段生成,可以看下lds文件

.rel.dyn : {
__rel_dyn_start = .;
*(.rel*)
__rel_dyn_end = .;
}
.dynsym : {
__dynsym_start = .;
*(.dynsym)
}

代码段重定位实现

代码重定位是在board_init_f中调用的

// addr_sp 最后的sp
// id gd结构的位置
// addr 重定位的位置
relocate_code(addr_sp, id, addr)

我们来计算下实际的代码段加bss段的大小

.globl _bss_start_ofs
_bss_start_ofs:
.word __bss_start - _start .globl _bss_end_ofs
_bss_end_ofs:
.word __bss_end__ - _start .globl _end_ofs
_end_ofs:
.word _end - _start // 查看具体的反汇编 00000040 <_TEXT_BASE>:
40: 00000000 .word 0x00000000 00000044 <_bss_start_ofs>:
44: 0006b568 .word 0x0006b568 00000048 <_bss_end_ofs>:
48: 000ae4e0 .word 0x000ae4e0 0000004c <_end_ofs>:
4c: 000736d8 .word 0x000736d8

可以看到整个的大小是_bss_end_ofs=0x000ae4e0,我们计算下

gd->relocaddr = addr=0x33ff,0000-0x000ae4e0=33F41B20
4k对齐 &~(4096-1)=fff
=33F4,1000

具体的汇编如下

	.globl	relocate_code
relocate_code:
mov r4, r0 /* save addr_sp */
mov r5, r1 /* save addr of gd */
mov r6, r2 /* save addr of destination */ /*这里设置新的sp*/
/* Set up the stack */
stack_setup:
mov sp, r4 adr r0, _start
cmp r0, r6
beq clear_bss /* skip relocation */
mov r1, r6 /* r1 <- scratch for copy_loop */
ldr r3, _bss_start_ofs
add r2, r0, r3 /* r2 <- source end address */ /*支持nor的复制,不支持nand的*/
copy_loop:
ldmia r0!, {r9-r10} /* copy from source address [r0] */
stmia r1!, {r9-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end address [r2] */
blo copy_loop

变量地址修改

程序的链接地址是0,访问全局变量、静态变量、调用函数时是使"基于0地址编译得到的地址

现在把程序复制到了SDRAM,需要修改代码,把"基于0地址编译得到的地址"改为新地址

程序里有些地址在链接时不能确定,要到运行前才能确定:fixabs

我们先来看下全局变量是怎么在汇编中使用的?查看文档全局变量反汇编与重定位.md,在文档中已经知道要怎么做了, 接下去看这个汇编的实现即可,uboot总结来说有两种情况,我只了解第一种情况

	从.rel.dyn 段中依次获得要修改的变量地址
如果下一个值Y是0x17
*(adr+offset)=*(adr+offset)+offset
如果下一个值Y的低8位是0x02,这里加的是绝对地址,和自身存储的地址值无关
*(adr+offset)=*[Y>>4+段dynsym_r10+4]+offset
  1. 正常的我们全局变量,指针的处理

  2. 某个lable存的值是个地址,他是一个绝对的偏移,与当前的位置无关,这个老师说是动态链接的时候是需要这样的,没有了解动态链接,暂时不去深究.

    从代码的意思来说,就是该地址是固定的,它是一个确定的位置*[Y>>4+段dynsym_r10+4]这个值就是表格里面死的值

比如我们代码偏移了offset=20,有两个地址单元【4】=14,【5】=15

  • 假设都为0x17标记,则【24】=14+20=34,【25】=15+20=35
  • 【5】0x02标记,则【24】=14+20=34,【25】=固定的值+20

在代码上体现是如下

/*
1. 计算偏移地址
2. 获得特殊段的 _dynsym_start_ofs _rel_dyn_start_ofs 位置
*/
/*
* fix .rel.dyn relocations
*/
ldr r0, _TEXT_BASE /* r0 <- Text base */
sub r9, r6, r0 /* r9 <- relocation offset */
ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */
add r10, r10, r0 /* r10 <- sym table in FLASH */
ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */
add r2, r2, r0 /* r2 <- rel dyn start in FLASH */
ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */
add r3, r3, r0 /* r3 <- rel dyn end in FLASH */ /*
从.rel.dyn 段中依次获得要修改的变量地址
如果下一个值Y是0x17
*(adr+offset)=*(adr+offset)+offset
如果下一个值Y的低8位是0x02,这里加的是绝对地址,和自身存储的地址值无关
*(adr+offset)=*[Y>>4+段dynsym_r10+4]+offset
*/ fixloop:
ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */
add r0, r0, r9 /* r0 <- location to fix up in RAM */
ldr r1, [r2, #4]
and r7, r1, #0xff
cmp r7, #23 /* relative fixup? */
beq fixrel
cmp r7, #2 /* absolute fixup? */
beq fixabs
/* ignore unknown type of fixup */
b fixnext
fixabs:
/* absolute fix: set location to (offset) symbol value */
mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */
add r1, r10, r1 /* r1 <- address of symbol in table */
ldr r1, [r1, #4] /* r1 <- symbol value */
add r1, r1, r9 /* r1 <- relocated sym addr */
b fixnext
fixrel:
/* relative fix: increase location by offset */
ldr r1, [r0]
add r1, r1, r9
fixnext:
str r1, [r0]
add r2, r2, #8 /* each rel.dyn entry is 8 bytes */
cmp r2, r3
blo fixloop

进一步每行代码分析如下

	调用之前的值

	mov	r4, r0	/* save addr_sp */
mov r5, r1 /* save addr of gd */
mov r6, r2 /* save addr of destination */ r0 链接地址这里是0
r6 目标地址
r9 这里就是flash与sdram的偏移地址了
/*
* fix .rel.dyn relocations
*/
ldr r0, _TEXT_BASE /* r0 <- Text base */
sub r9, r6, r0 /* r9 <- relocation offset */ -----------------------------------------
r0 链接地址这里是0
r6 目标地址
r9 这里就是flash与sdram的偏移地址了
-------------------------------------------- ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */
add r10, r10, r0 /* r10 <- sym table in FLASH */ -----------------------------------------------
_dynsym_start_ofs:
.word __dynsym_start - _start
r10 就是 __dynsym_start 在flash 的实际地址
----------------------------------------------- ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */
add r2, r2, r0 /* r2 <- rel dyn start in FLASH */ -----------------------------------------------
_rel_dyn_start_ofs:
.word __rel_dyn_start - _start
r2 就是 __rel_dyn_start 在flash 的实际地址
----------------------------------------------- ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */
add r3, r3, r0 /* r3 <- rel dyn end in FLASH */ -----------------------------------------------
_rel_dyn_end_ofs:
.word __rel_dyn_end - _start
r3 就是 __rel_dyn_end 在flash 的实际地址
----------------------------------------------- -----------------------------------------------
总结来说就是先确定了具体在flash上的地址
.rel.dyn :
{
__rel_dyn_start = .; ---------r2
*(.rel*)
__rel_dyn_end = .; ---------r3
}
.dynsym :
{
__dynsym_start = .; ----------r10
*(.dynsym)
} 0006b568 <__rel_dyn_start>: //接下去用A标志这里 r2
6b568: 00000020 .word 0x00000020
6b56c: 00000017 .word 0x00000017
6b570: 00000024 .word 0x00000024
6b574: 00000017 .word 0x00000017
6b578: 00000028 .word 0x00000028
6b57c: 00000017 .word 0x00000017
..................................................end=r3 00073608 <__dynsym_start>: //接下去用B标志这里 r10
...
73624: 00010003 .word 0x00010003
73628: 00000000 .word 0x00000000
7362c: 00068de4 .word 0x00068de4
73630: 00000000 .word 0x00000000
73634: 00050003 .word 0x00050003
73638: 00000049 .word 0x00000049
7363c: 0006b568 .word 0x0006b568
----------------------------------------------- 这里的 r2 可以先理解为是nor上的地址 也就是加载地址 fixloop: // r2 表示当前从 A 取址的地址 ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */
// 从A表示的地址 取出具体的值
add r0, r0, r9 /* r0 <- location to fix up in RAM */
// 将其值加上offset
ldr r1, [r2, #4]
and r7, r1, #0xff
cmp r7, #23 /* relative fixup? */
//取出下一个A的值,如果是0x17 跳转到 fixrel
// 如果是0x02 跳转到 fixabs //--- r0 是修正过的地址值
//--- r1 是__rel_dyn_start下一个的值
beq fixrel
cmp r7, #2 /* absolute fixup? */
beq fixabs
/* ignore unknown type of fixup */
b fixnext
fixabs: //到这里的时候 从A取出来的值已经加上偏移了 /* absolute fix: set location to (offset) symbol value */ mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */
// r1 右移4位 这里应该是后四位无效用来表示 #2
// 猜测 r1 这里表示的是 B的序号 也就是第几个了 add r1, r10, r1 /* r1 <- address of symbol in table */
ldr r1, [r1, #4] /* r1 <- symbol value */
add r1, r1, r9 /* r1 <- relocated sym addr */
b fixnext
fixrel:
//到这里的时候 从A取出来的值r0已经加上偏移了
/* relative fix: increase location by offset */
ldr r1, [r0]
// 从重定位后的ram中取出值.老师的笔记这里写错了哦 #########################
add r1, r1, r9
// 将这个值加上offset r1
fixnext:
str r1, [r0]
add r2, r2, #8 /* each rel.dyn entry is 8 bytes */
cmp r2, r3
blo fixloop

参考

内核黑科技之DEFINE宏

全局变量反汇编与重定位

uboot2012(一)分析重定位的更多相关文章

  1. 鸿蒙内核源码分析(重定位篇) | 与国际接轨的对外部发言人 | 百篇博客分析OpenHarmony源码 | v55.01

    百篇博客系列篇.本篇为: v55.xx 鸿蒙内核源码分析(重定位篇) | 与国际接轨的对外部发言人 | 51.c.h.o 加载运行相关篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应用程 ...

  2. PE文件结构详解(六)重定位

    前面两篇 PE文件结构详解(四)PE导入表 和 PE文件结构详解(五)延迟导入表 介绍了PE文件中比较常用的两种导入方式,不知道大家有没有注意到,在调用导入函数时系统生成的代码是像下面这样的: 在这里 ...

  3. uboot重定位代码分析(转)

    概述 重定位(relocate)代码将BootLoader自身由Flash复制到SDRAM,以便跳转到SDRAM执行.之所以需要进行重定位是因为在Flash中执行速度比较慢,而系统复位后总是从0x00 ...

  4. linux从head.s到start_kernelstart_kernel之---内核重定位后分析

    参考: https://biscuitos.github.io/blog/ARM-BOOT/ zImage 重定位之后实践 zImage 重定位之后,ARM 将 pc 指针指向了重定位 zImage ...

  5. linux从head.s到start_kernelstart_kernel之---内核解压到重定位分析

    一: arm linux 内核生成过程 1. 依据arch/arm/kernel/vmlinux.lds 生成linux内核源码根目录下的vmlinux,这个vmlinux属于未压缩,带调试信息.符号 ...

  6. u-boot移植(四)---修改前工作:代码流程分析3---代码重定位

    一.重定位 1.以前版本的重定位 2.新版本 我们的程序不只涉及一个变量和函数,我们若想访问程序里面的地址,则必须使用SDRAM处的新地址,即我们的程序里面的变量和函数必须修改地址.我们要修改地址,则 ...

  7. uboot搬移部分和重定位部分的代码分析

    来看一下搬移部分和重定位部分的代码: relocate: /* 把U-BOOT重新定位到RAM*/          //r0=0; adr r0, _start /* r0是代码的当前位置*/ ld ...

  8. [PE结构分析] 10.基址重定位

    源代码如下: typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock; // WORD Type ...

  9. 小甲鱼PE详解之基址重定位详解(PE详解10)

    今天有一个朋友发短消息问我说“老师,为什么PE的格式要讲的这么这么细,这可不是一般的系哦”.其实之所以将PE结构放在解密系列继基础篇之后讲并且尽可能细致的讲,不是因为小甲鱼没事找事做,主要原因是因为P ...

随机推荐

  1. API测试工具SoapUI & Postman对比分析

    本文由葡萄城技术团队于博客园原创并首发 转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 最近公司要引入API测试工具,经过调查和了解,最终决定在SoapUI ...

  2. Spring WebFlux 响应式编程学习笔记(一)

    各位Javaer们,大家都在用SpringMVC吧?当我们不亦乐乎的用着SpringMVC框架的时候,Spring5.x又悄(da)无(zhang)声(qi)息(gu)的推出了Spring WebFl ...

  3. gitbook 入门教程之主题插件

    主题插件 目前 gitbook 提供三类文档: Book 文档,API 文档和 FAQ 文档. 其中,默认的也是最常使用的就是 Book 文档,如果想要了解其他两种文档模式,需要引入相应的主题插件. ...

  4. node.js解析微信消息推送xml格式加密的消息

    之前写过一个解密json格式加密的,我以为xml的和json的差不多,是上上个星期五吧,我的同事也是在做微信公众号里面的消息推送解密,发现好像只能使用xml加密格式的发送到服务器,我们去年也做过企业微 ...

  5. 简单易懂的单元测试框架-gtest(二)

    简介     事件机制用于在案例运行前后添加一些操作(相当于挂钩函数).目前,gtest提供了三种等级的事件,分别: 全局级,所有案例执行的前后 TestSuite级,某一个案例集的前后 TestCa ...

  6. Git源代码管理

    一. 分支管理 使用 git 进行源代码管理,一般将某个项目的所有分支分为以下几条主线 1. Master 顾名思义,既然名字叫 Master ,那么该分支就是主分支的意思. master 分支永远是 ...

  7. socket.io 出现的WebSocket is closed before the connection is established

    WebSocket is closed before the connection is established 最近socket.io是挺流行的,幼麟棋牌和一些好的开源项目也使用这个框架,在搭建其平 ...

  8. HashMap源码分析(二)

    前言:上篇文章,笔者分析了jdk1.7中HashMap的源码,这里将对jdk1.8的HashMap的源码进行分析. 注:jdk版本:jdk1.8.0_172 1.再看put操作 public V pu ...

  9. 数据库MySQL和Redis实践

    1.关于数据库设计的那些事 2.MySQL 3.Redis

  10. ESP8266最小系统

    http://www.dnsj88.com/Products/esp12f.html https://gitai.me/2017/04/Re-Zero-Starting-in-IoT/