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. 激活函数,Batch Normalization和Dropout

    神经网络中还有一些激活函数,池化函数,正则化和归一化函数等.需要详细看看,啃一啃吧.. 1. 激活函数 1.1 激活函数作用 在生物的神经传导中,神经元接受多个神经的输入电位,当电位超过一定值时,该神 ...

  2. HADOOP回顾

    1. hadoop 分为四大模块 common  其他组件的公共一来模块 HDFS 分布式模块提供高吞吐量的数据访问 mapreduce 分布式计算模块 yarn 作业调度和集群资源管理模块 2. 安 ...

  3. Json序列化指定输出字段 忽略属性

    DataContract 服务契约定义了远程访问对象和可供调用的方法,数据契约则是服务端和客户端之间要传送的自定义数据类型. 一旦声明一个类型为DataContract,那么该类型就可以被序列化在服务 ...

  4. insmod某个内核模块时提示“Failed to find the folder holding the modules”如何处理?

    答: 创建/lib/modules/$(uname -r)目录,命令如下: mkdir /lib/modules/$(uname -r)

  5. PHP操作文件常用函数

    [获取文件信息的函数] basename($path[,扩展名]) 返回文件路径中去掉路径后的文件名称."/root/a.txt"输出a.txt;带上.txt输出a. dirnam ...

  6. AndroidStudio制作Nine-Patch【.9】图片

    使用AndroidStudio制作Nine-Patch[.9]图片,以及为什么要制作Nine-Patch[.9]图片[以聊天气泡为例]   本文链接:https://blog.csdn.net/She ...

  7. sql中union,union all没有兼顾到的内容

    今日遇到一个问题,两张表联合取交集去重,但是需要把某一字段相同的也给去掉 union all : 联合,没有取交集 union :联合取交集(仅针对所有字段相同的去重) 解决方案:将联合的数据作为一个 ...

  8. 目标检测中的选择性搜索-selective search-没弄

    https://blog.csdn.net/small_munich/article/details/79595257 https://www.cnblogs.com/zyly/p/9259392.h ...

  9. MySQL批量导入Excel数据

    MySQL批量导入Excel数据 1.确定需要导入数据的表名称以及字段,然后在新建的Excel表中,按照表字段正确排序:(注:(Excel文件的名称最好和数据库的名称一致,sheet表的名字最好和表名 ...

  10. hue集成mysql找不到 libmysqlclient.so.16问题解决

    首先我的配置文件如下,这个是没有问题的 但是在重启hue连接mysql时,却发生了如下问题: 这个错误的意思就是没有找到libmysqlclient_r.so.16这个文件,可能是我安装的mysql有 ...