ref:

https://blog.csdn.net/dhauwd/article/details/78566668

https://blog.csdn.net/yueqian_scut/article/details/39004727

https://blog.csdn.net/Egean/article/details/84889565

https://www.cnblogs.com/zafu/p/7399859.html


涉及领域:

    裸机程序,uboot,Linux kernel
 

1、为什么需要重定位?

    大部分的程序是不需要重定位的,但是有时候需要。
    最常见的例子就是我们的UBOOT,因为我们的UBOOT有200多KB,但是我们开始BL0的地方只有96KB。所以我们需要在96KB之前进行重定位,使开发板能够进行重定位。如果代码不是位置无关码,代码必须放在链接地址开始的地方,程序才可以正常运行,否则的话当PC去访问、执行某个变量名、函数名对应地址上的代码时就会找不到,接着程序无疑就是跑飞。
 

2、什么是重定位?

 
    重定位:把代码搬移到你想要的地址,本来程序是运行在运行地址处的,你可以通过重定位搬移到链接地址处。
 
    链接地址: 编译器对代码中的变量名、函数名等东西进行一个地址的编排,赋予这些抽象的东西一个地址,然后在程序中访问这些变量名、函数名就是在访问一些地址,这些地址我们称之为编译地址。
 
    运行地址:是指程序指令真正运行的地址,是由用户指定的,用户将运行地址烧录到哪里,也就是PC当前执行指令所在的实际地址,就是运行的地址。也就是真实在程序中运行的地址。
 

3、重定位的基础知识

 
(1)大部分指令是位置有关编码。
 
位置无关码(PIC, position independent code):
    汇编源文件被编码成二进制可执行程序后与位置无关。有些特别的指令,可以跟地址没有关系。也就是说这些代码实际运行时,不管放在哪里都能正常运行。
 
位置有关码:
    汇编编码成二进制可执行程序后和内存地址是有关的。
 
PS:
    我们在设计一个程序时,会给这个程序指定一个运行地址。就是说我们在写程序时,其实我们是知道我们程序将来被运行的地址的。
    必须给编译器和链接器指定这个地址才行,最后得到二进制程序。
    理论上和你指定的运行地址是有关的,这就叫做位置有关代码。
 
 
(2)对于位置有关码来说:最终执行时的运行地址和编译链接时给定的链接地址必须相同,否则一定会出错。
 
如果编译时 使用-Ttext 0x0来指定链接地址是0x0,这意味着我们认为这个程序将来会放在这个内存地址中运行。但是实际上我们运行的地址是下载在开发板的地址0xd0020010。因为是位置无关码,所以运行程序来是没有什么问题的。而且我们开发板对这些程序进行了映射,所以说这是一个偶然的情况。
 
 
 
(3)我们再来分析一下S5PV210的启动过程。
 
 
 
官方建议的启动过程(假定你的Bootloader为80KB)
    开机启动,执行BL0,BL0会加载外部启动设备中的bootloader的前16KB到SRAM,
        (BL0是厂家事先固化好的程序)
 
    校验BL1,运行BL1
 
    BL1在运行时,初始化外部DDR,加载剩余的64kb代码到 BL2中 ( 64 = 80 - 16)
 
    运行BL2,初始化DDR,并且将OS搬运到DDR
 
    执行OS,启动完成。
 
 
 
UBOOT实际上的启动的方法:
(由于 BL2 的空间也太小了,使用起来非常有局限,所以uboot的设计者干脆在BL1以后,连同BL2与OS有关的直接放到DDR上运行了)
 
    先开机上电,BL0运行,BL0会加载外部启动设备中的UBOOT的前16KB(BL1)到SRAM中去运行,
 
    BL1运行会初始化DDR。然后将整个UBOOT,搬运到我们的DDR中。
 
    从SRAM中直接长跳转到DDR中继续执行我们的UBOOT。直到UBOOT完全启动。
        长跳转的意思就是从SRAM中跳转到DDR中。UBOOT启动后在命令行中去执行OS。
 
 
 

4、从源码到可执行程序的步骤:预编译、编译,链接、[strip]

 
预编译:    比如C中的宏定义就是由预编译器处理的,注释等也是由预编译器处理的。
编译:        编译器来执行,把源码中的.c/.s文件转换为.o文件。
链接:        链接器来执行,把.o文件中的各种函数(段)按照一定的规则(即使不用用链接脚本指定,链接器也有默认的固定的顺序)链接到一起,形成可执行程序。
    链接的本质是规则文件,它指明了一种行动的规则,它是我们程序员用来指挥链接器工作的一种语言。
    链接器会参考链接脚本来处理我们.o文件哪些段,将其链接成一个可执行程序。
 
strip:          把可执行程序中的符号信息给拿掉,以节省空间,一般可以节省3分之一的空间。这样就从 elf 文件 转换 为 bin 文件
 
 
 

5、链接脚本存在的意义

链接脚本用来指定编译时的一些选项,使得程序能够按照开发者的意志进行指定的排布,也为了在某些特定的场合满足需求。

1.代码段 (.text)
代码段(code segment/text segment)通常是指用来存放 程序执行代码 的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于 只读 , 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些 只读的常数变量 ,例如字符串常量等。程序段为程序代码在内存中的映射。一个程序可以在内存中多有个副本。
 
 
2.数据段(.data)
数据段就是C语言中有显示的初始化为非0的全局变量。数据段(data segment)通常是指用来存放程序中 已初始化 的 全局变量 的一块内存区域。数据段属于静态内存分配。
 
3.BSS段(.bss) ,又叫做ZI段,零初始化段,
通常是指用来存放程序中未初始化或初始化为0的全局变量和静态变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。特点是可读写的,在程序执行之前BSS段会自动清0。bss段的存放是指为其预留空间(占位符)BSS段在可执行文件中时候不占磁盘空间,要运行的时候才分配空间并清0.
 
4.自定义段
由我们程序员自己定义,段的属性和特征也由我们自己定义。
 
 
 
 
 
在移植 uboot 时,接触到一个概念叫做 位置无关码,那么与它对应的就是位置有关码。提到这两个概念就还得提一提链接地址、加载地址。
 
链接地址,链接脚本里指定的,理论上程序运行时所处的地址。在编译时,编译器会根据链接地址来翻译位置有关码。
 
加载地址,程序运行时,实际所处的地址。
 
    位置无关码,位置有关码,是相对于一条指令的正常目的来说的。比如 ldr r0 ,=标号,它的正常目的是取得标号处的地址,对于这个目的,它是位置有关码,运行的地址不对就获取不到正确的标号地址,其实它无论在哪都是获取的程序加载地址等于链接地址时,标号的地址,如果你就是想要这个值,那么用这条指令是非常正确的,就不用理会什么位置无关码,位置有关码的概念了,这一点非常重要。
 
    因此,当加载地址不等于链接地址时,并不是不可以用位置无关码,而是要看你用位置无关码是否达到了你想要的目的。
 
    位置无关码,依赖于程序当前运行的PC值,进行相对的跳转,导致的结果就是,无论代码在哪,总能达到指令的正常目的,因此是位置无关的。
 
    位置有关码,不依赖当前PC值,是绝对跳转,只有程序运行在链接地址处时,才能达到指令的正常目的,因此是位置有关系的。
 
    下面,我们来看常用的汇编指令以及C语言中哪些操作是位置有关码,哪些是位置无关码。
 
SECTIONS {  
    . = 0x33f80000;  
    .text : { *(.text) }  
      
    . = ALIGN(4);  
    .rodata : {*(.rodata*)}   
      
    . = ALIGN(4);  
    .data : { *(.data) }  
      
    . = ALIGN(4);  
    __bss_start = .;  
    .bss : { *(.bss)  *(COMMON) }  
    __bss_end = .;  
}  
 
.text  
.global _start  
_start:  
  
    bl close_watch_dog      /* 相对跳转,位置无关 */  
    bl _start  
    adr r0, close_watch_dog /* 获取标号地址,位置无关 */  
      
    ldr r0, SMRDATA         /* 获取标号处的值,位置无关 */  
  
    ldr r0, =0x12345678  
    ldr r0, =SMRDATA        /* 获取标号地址,位置有关 */  
    ldr r0, =main           /* 获取函数名的地址,位置有关 */  
    ldr r0 ,=__bss_start    /* 获取链接脚本里标号的地址,位置有关 */  
  
      
close_watch_dog:  
    mov r1, #0  
    str r1, [r0]  
    mov pc, lr  
      
SMRDATA:  
    .word  0x22111120  
 
int a;  
void abc(){  
    a = 2;  
}  
int main(){  
    int b;  
    a =1 ;  
    b =1 ;  
    abc();  
    return 0;  
}
如果加载地址为 0 ,那么代码将按照下面的顺序排放
 
00000000 <_start>:  
00000000:   eb000006    bl  33f80020 <close_watch_dog>  
00000004:   ebfffffd    bl  33f80000 <_start>  
00000008:   e28f0010    add r0, pc, #16  
0000000c:   e59f0018    ldr r0, [pc, #24]   ;   
00000010:   e59f0018    ldr r0, [pc, #24]   ;   
00000014:   e59f0018    ldr r0, [pc, #24]   ;   
00000018:   e59f0018    ldr r0, [pc, #24]   ;   
0000001c:   e59f0018    ldr r0, [pc, #24]   ;   
  
00000020 <close_watch_dog>:  
00000020:   e3a01000    mov r1, #0  
00000024:   e5801000    str r1, [r0]  
00000028:   e1a0f00e    mov pc, lr  
  
0000002c <SMRDATA>:  
0000002c:   22111120    andscs  r1, r1, #8  
00000030:   12345678    eorsne  r5, r4, #125829120  ; 0x7800000  
00000034:   33f8002c    mvnscc  r0, #44 ; 0x2c  
00000038:   33f80064    mvnscc  r0, #100    ; 0x64  
0000003c:   33f800a0    mvnscc  r0, #160    ; 0xa0  
  
00000040 <abc>:  
00000040:   e52db004    push    {fp}        ; (str fp, [sp, #-4]!)  
00000044:   e28db000    add fp, sp, #0  
00000048:   e59f3010    ldr r3, [pc, #16]   ; 33f80060 <abc+0x20>  
0000004c:   e3a02002    mov r2, #2  
00000050:   e5832000    str r2, [r3]  
00000054:   e28bd000    add sp, fp, #0  
00000058:   e8bd0800    pop {fp}  
0000005c:   e12fff1e    bx  lr  
00000060:   33f800a0    mvnscc  r0, #160    ; 0xa0  
  
00000064 <main>:  
00000064:   e92d4800    push    {fp, lr}  
00000068:   e28db004    add fp, sp, #4  
0000006c:   e24dd008    sub sp, sp, #8  
00000070:   e59f3024    ldr r3, [pc, #36]   ; 33f8009c <main+0x38>  
00000074:   e3a02001    mov r2, #1  
00000078:   e5832000    str r2, [r3]  
0000007c:   e3a03001    mov r3, #1  
00000080:   e50b3008    str r3, [fp, #-8]  
00000084:   ebffffed    bl  33f80040 <abc>  
00000088:   e3a03000    mov r3, #0  
0000008c:   e1a00003    mov r0, r3  
00000090:   e24bd004    sub sp, fp, #4  
00000094:   e8bd4800    pop {fp, lr}  
00000098:   e12fff1e    bx  lr  
0000009c:   33f800a0    mvnscc  r0, #160    ; 0xa0
 
如果加载地址为0x33f80000 则按照下边的顺序排放
 
33f80000 <_start>:  
33f80000:   eb000006    bl  33f80020 <close_watch_dog>  
33f80004:   ebfffffd    bl  33f80000 <_start>  
33f80008:   e28f0010    add r0, pc, #16  
33f8000c:   e59f0018    ldr r0, [pc, #24]   ; 33f8002c <SMRDATA>  
33f80010:   e59f0018    ldr r0, [pc, #24]   ; 33f80030 <SMRDATA+0x4>  
33f80014:   e59f0018    ldr r0, [pc, #24]   ; 33f80034 <SMRDATA+0x8>  
33f80018:   e59f0018    ldr r0, [pc, #24]   ; 33f80038 <SMRDATA+0xc>  
33f8001c:   e59f0018    ldr r0, [pc, #24]   ; 33f8003c <SMRDATA+0x10>  
  
33f80020 <close_watch_dog>:  
33f80020:   e3a01000    mov r1, #0  
33f80024:   e5801000    str r1, [r0]  
33f80028:   e1a0f00e    mov pc, lr  
  
33f8002c <SMRDATA>:  
33f8002c:   22111120    andscs  r1, r1, #8  
33f80030:   12345678    eorsne  r5, r4, #125829120  ; 0x7800000  
33f80034:   33f8002c    mvnscc  r0, #44 ; 0x2c  
33f80038:   33f80064    mvnscc  r0, #100    ; 0x64  
33f8003c:   33f800a0    mvnscc  r0, #160    ; 0xa0  
  
33f80040 <abc>:  
33f80040:   e52db004    push    {fp}        ; (str fp, [sp, #-4]!)  
33f80044:   e28db000    add fp, sp, #0  
33f80048:   e59f3010    ldr r3, [pc, #16]   ; 33f80060 <abc+0x20>  
33f8004c:   e3a02002    mov r2, #2  
33f80050:   e5832000    str r2, [r3]  
33f80054:   e28bd000    add sp, fp, #0  
33f80058:   e8bd0800    pop {fp}  
33f8005c:   e12fff1e    bx  lr  
33f80060:   33f800a0    mvnscc  r0, #160    ; 0xa0  
  
33f80064 <main>:  
33f80064:   e92d4800    push    {fp, lr}  
33f80068:   e28db004    add fp, sp, #4  
33f8006c:   e24dd008    sub sp, sp, #8  
33f80070:   e59f3024    ldr r3, [pc, #36]   ; 33f8009c <main+0x38>  
33f80074:   e3a02001    mov r2, #1  
33f80078:   e5832000    str r2, [r3]  
33f8007c:   e3a03001    mov r3, #1  
33f80080:   e50b3008    str r3, [fp, #-8]  
33f80084:   ebffffed    bl  33f80040 <abc>  
33f80088:   e3a03000    mov r3, #0  
33f8008c:   e1a00003    mov r0, r3  
33f80090:   e24bd004    sub sp, fp, #4  
33f80094:   e8bd4800    pop {fp, lr}  
33f80098:   e12fff1e    bx  lr  
33f8009c:   33f800a0    mvnscc  r0, #160    ; 0xa0  
  
Disassembly of section .bss:  
  
33f800a0 <a>:  
33f800a0:   00000000    andeq   r0, r0, r0
 
一、B BL指令
 
bl close_watch_dog
33f80000:  eb000006   bl33f80020 <close_watch_dog> 
 
 
 
    b 是相对跳转:PC + 偏移值 (PC值等于当前地址+8)
 
    偏移值:机器码 0xeb000006 低 24位 0x000006 按符号为扩展为 32 位 0x00000006 正数,向后跳转 0x6 个 4字节 也就是 0x1c
 
    1、加载地址0:0 + 8 + 0x1c = 0x20 正确跳转
 
    2、加载地址0x3ff80000:0x3ff80000 + 8 + 0x1c = 0x33f80020 正确跳转

 
bl _start
33f80004:  ebfffffd  bl33f80000 <_start>
 
    偏移值:机器码 0xebfffffd 低 24位 fffffd 按符号位扩展为 32 位 0xfffffffd 负数(-3),向前跳转 0x3 个 4字节 也就是 0xc
 
 
    1、加载地址0:4 + 8 - 0xc = 0 正确跳转
 
    2、加载地址0x3ff80000:    0x3ff80004 + 8 + 0xc = 0x33f80000 正确跳转
 
    通过以上分析,我们知道B是相对跳转,位置无关码,也可以知道为什么32为arm指令集,B的范围为正负32M了,24位去掉1位符号位,恰好等于32M。
 
 
 
二、ADR

 
adr r0, close_watch_dog     /* 获取标号处的地址,位置无关 */
33f80008: e28f0010  add  r0,  pc,  #16 
 
 
    1、加载地址0:0 + 8 + 16 = 0x20 正确
 
    2、加载地址0x3ff80000:0x3ff80008 + 8 + 16 = 0x33f80020 正确
 
    adr 获取的是标号处的“实际”地址,标号在哪就是哪个地址,跟位置无关,总能获得想要的值。
 
 
 
三、LDR

ldr r0, SMRDATA       /* 获取标号处的值,位置无关 */
33f8000c:   e59f0018  ldr   r0,  [pc, #24];  33f8002c <SMRDATA>
 
    ldr r0, SMRDATA       /* 获取标号处的值,位置无关 */
 
    33f8000c:e59f0018ldrr0, [pc, #24]; 33f8002c <SMRDATA>
 
    1、加载地址0:r0 = c + 8 + 24 = 0x2c 处的值 0x22111120 正确
 
    2、加载地址0x3ff80000:r0 = 0x3ff8000c + 8 + 24 = 0x33f8002c处的值 0x22111120 正确

  ldr r0, =0x12345678   /* 常数赋值,位置无关 */
 
 33f80010: e59f0018  ldr  r0, [pc, #24];  33f80030 <SMRDATA+0x4>
 
 
    1、加载地址0:r0 = 0x10 + 8 + 24 = 0x30 处的值 0x12345678 正确
 
    2、加载地址0x3ff80000:r0 = 0x3ff80010 + 8 + 24 = 0x33f80030处的值 0x12345678 正确
 

ldr r0, =SMRDATA            /* 获取标号地址,位置有关 */
 
33f80014: e59f0018  ldrr0,  [pc, #24];  33f80034 <SMRDATA+0x8>
 
    1、加载地址0:r0 = 0x14 + 8 + 24 = 0x34 处的值 33f8002c 与标号实际地址(2c)不符合,不正确
 
    2、加载地址0x3ff80000:r0 = 0x3ff80014 + 8 + 24 = 0x33f80034 处的值 33f8002c 正确
 
 
 
    ldr r0, =main/* 获取函数名的地址,位置有关 */
    ldr r0 ,=__bss_start /* 获取链接脚本里标号的地址,位置有关 */
 
    这俩和 ldr r0, =SMRDATA 一致,位置有关,在0地址处运行不正确。
 
四、C函数
 
    1、全局变量
 
00000040 <abc>:  
00000040:   e52db004    push    {fp}        ; (str fp, [sp, #-4]!)  
00000044:   e28db000    add fp, sp, #0  
00000048:   e59f3010    ldr r3, [pc, #16]   ; 33f80060 <abc+0x20>  
0000004c:   e3a02002    mov r2, #2  
00000050:   e5832000    str r2, [r3]  
00000054:   e28bd000    add sp, fp, #0  
00000058:   e8bd0800    pop {fp}  
0000005c:   e12fff1e    bx  lr  
00000060:   33f800a0    mvnscc  r0, #160    ; 0xa0  
 
 
 
 
000000a0 <a>:  
000000a0:   00000000    andeq   r0, r0, r0  
 
 
 
33f80040 <abc>:  
33f80040:   e52db004    push    {fp}        ; (str fp, [sp, #-4]!)  
33f80044:   e28db000    add fp, sp, #0  
33f80048:   e59f3010    ldr r3, [pc, #16]   ; 33f80060 <abc+0x20>  
33f8004c:   e3a02002    mov r2, #2  
33f80050:   e5832000    str r2, [r3]  
33f80054:   e28bd000    add sp, fp, #0  
33f80058:   e8bd0800    pop {fp}  
33f8005c:   e12fff1e    bx  lr  
33f80060:   33f800a0    mvnscc  r0, #160    ; 0xa0  
 
 
 
 
33f800a0 <a>:  
33f800a0:   00000000    andeq   r0, r0, r0  
 
 
r3 为全局变量 a 的地址,a 是存放在 0起始的地址还是0x33f80000起始的地址,它都认为 a 的地址是 0x33f800a0 。因此,C函数中调用全局变量是位置有关码。
 
 
    2、函数调用
 
    33f80084:  ebffffed  bl  33f80040 <abc>
 
    由于 main 函数和 abc 函数挨得比较近,在32M范围之内,因此被翻译成了一条 bl 指令,那么与位置无关。
 
    如果,调用的函数比较远,大于32M的话,我认为是与位置有关系的,这个不再验证了。
 
 
 
    3、局部变量
 
    局部变量在函数刚开始的地方被压入栈,赋值语句被翻译成:
 
    33f8007c:   e3a03001  mov  r3, #1
    33f80080:   e50b3008
    str r3, [fp, #-8]
 
    位置无关。
 
 

uboot 与 代码重定位的更多相关文章

  1. s3c6410_uboot中的代码重定位(nand->sdram)

    本文仅探讨s3c6410从nand flash启动u-boot时的代码重定位过程 参考: 1)<USER'S MANUAL-S3C6410X>第二章 MEMORY MAP 第八章 NAND ...

  2. s3c2440裸机-代码重定位(2.编程实现代码重定位)

    代码重定位(2.编程实现代码重定位) 1.引入链接脚本 我们上一节讲述了为什么要重定位代码,那么怎么去重定位代码呢? 上一节我们发现"arm-linux-ld -Ttext 0 -Tdata ...

  3. s3c2440裸机-代码重定位、清bss的改进和位置无关码

    1.代码重定位的改进 用ldr.str代替ldrb, strb加快代码重定位的速度. 前面重定位时,我们使用的是ldrb命令从的Nor Flash读取1字节数据,再用strb命令将1字节数据写到SDR ...

  4. s3c2440裸机-代码重定位(1.重定位的引入,为什么要代码重定位)

    1.重定位的引入(为什么要代码重定位) 我们知道s3c2440的cpu从0地址开始取指令执行,当从nor启动时,0地址对应nor,nor可以像内存一样读,但不能像内存一样写.我们能够从nor上取指令执 ...

  5. Linux从头学06:16张结构图,彻底理解【代码重定位】的底层原理

    作 者:道哥,10+年的嵌入式开发老兵. 公众号:[IOT物联网小镇],专注于:C/C++.Linux操作系统.应用程序设计.物联网.单片机和嵌入式开发等领域. 公众号回复[书籍],获取 Linux. ...

  6. S3C2440—10.代码重定位

    文章目录 一.启动方式 1.1 NAND FLASH 启动 1.2 NOR FLASH 启动 二. 段的概念 2.1 重定位数据段 2.2 加载地址的引出 三.链接脚本 3.1 链接脚本的引入 3.2 ...

  7. 代码重定位和位置无关码——运行于nor flash

    通过前面的学习,我们知道,把可执行程序从一个位置复制到另一个位置的过程叫做重定位. 现在有两种方式,第一种是只重定位data段到内存(sdram),为什么需要重定位?因为有些flash的写操作,不是简 ...

  8. U-Boot中关于TEXT_BASE,代码重定位,链接地址相关说明

    都知道U-BOOT分为两个阶段,第一阶段是(~/cpu/arm920t/start.S中)在FLASH上运行(一般情况 下),完成对硬件的初始化,包括看门狗,中断缓存等,并且负责把代码搬移到SDRAM ...

  9. s3c2440代码重定位和段的引入——学以致用,综合Makefile的锻炼

    对于2440而言,nand启动,nand的前4k内容由硬件复制到sram. nor flash,可以像内存一样读,但是不能像内存一样写,执行写操作需要特殊的操作. 程序中包含有需要写的全局或者静态变量 ...

随机推荐

  1. cross socket和msgpack的数据序列和还原

    cross socket和msgpack的数据序列和还原 procedure TForm1.Button1Click(Sender: TObject); begin var pack: TSimple ...

  2. TynSerial序列(还原)TFDMemTable

    TynSerial序列(还原)TFDMemTable 1)TFDMemTable查询数据 procedure TForm1.Qrys(accountno, sql, sql2: string; Dat ...

  3. office web apps 在线问答预览

    最近在做项目时,需要用到在线文档预览,看过明道的一篇搭建office web apps服务的文章,但是由于时间的关系,没有仔细研究,这几天有时间,就拿出来研究了下,折腾了几天终于完成了部署,然后就搬过 ...

  4. js的Map实例

    1.创建实例 let map= new Map(); // 创建 2.对map的写入 // 要添加的对象 let obj1 = {name:'张三', sex:'boy',age: 21}; let ...

  5. ISO/IEC 9899:2011 条款5——5.1.2 执行环境

    5.1.2 执行环境 1.定义了两个执行环境:独立式(freestanding)以及宿主的(hosted).在这两种情况下,当一个派定的C函数被执行环境调用时,程序就启动.所有具有静态存储周期的对象应 ...

  6. Zend Studio汉化失败,如何给Zend Studio进行汉化

    首先,相信看我这篇博文的人也都遇到了和博主我一样的烦恼,就是汉化Zend Studio失败! 话不多说! 方案一,在线安装汉化包 Help–>Install New Software—>W ...

  7. 关于define('DISCUZ_ROOT', substr(dirname(__FILE__), 0, -7));的理解

    关于define('DISCUZ_ROOT', substr(dirname(__FILE__), 0, -7));的理解 define('DISCUZ_ROOT',  substr(dirname( ...

  8. LeetCode_101. Symmetric Tree

    101. Symmetric Tree Easy Given a binary tree, check whether it is a mirror of itself (ie, symmetric ...

  9. (五)Centos之目录处理命令

    一.目录处理命令 linux中 关于目录 有几个重要概念:一个是 / 根目录  还有一个当前用户的家目录 比如 root用户的家目录是 /root  ,普通用户的家目录是/home/xxx 下,如下图 ...

  10. 完全解读 margin 标签

    你真的了解margin吗?你知道margin有什么特性吗?你知道什么是垂直外边距合并?margin在块元素.内联元素中的区别?什么时候该用 padding而不是margin?你知道负margin吗?你 ...