JHHK

欢迎来到我的个人网站
行者常至 为者常成

符号表

目录

在xcconfig文件内配置

.xcconfig文件

//1.
//解决xcconfig文件的冲突
#include "../../../Pods/Target Support Files/Pods-LCClientDemo/Pods-LCClientDemo.debug.xcconfig"


//2.
// -p : 不排序
// -a : 显示所有符号,包括调试符号
MACHO_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/LCClientDemo.app/LCClientDemo
CMD = nm
CMD_FLAG = -pa ${MACHO_PATH}
TTY = /dev/ttys002


//3.
//$(inherited) 继承之前的设定,解决冲突
//Xlinker 传给连接器的参数
//查看连接器参数:man ld 然后输入 / -S
//-S  Do not put debug information (STABS or DWARF) in the output file.
OTHER_LDFLAGS = $(inherited) -Xlinker -S


//4.
//设置条件
OTHER_LDFLAGS[config=Debug][sdk=iphonesimulator*][arch=x86_64] = $(inherited) -framework "Cat"


//5.
//解决 // 问题
//URL_TEST = http://127.0.0.1
FLASH = /
URL_TEST = http:${FLASH}/127.0.0.1

Build Phases -> Run Script

#./appSign.sh
echo "LC_Catxxx" > /dev/ttys005
echo "${LXY_TEST}" > /dev/ttys005
echo "$SRCROOT" > /dev/ttys005
echo "${MACHO_PATH}" > /dev/ttys005

/bin/sh "$SRCROOT/LCClientDemo/Script/xcode_run_cmd.sh"

xcode_run_cmd.sh

#!/bin/sh

RunCommand() {
  #判断全局字符串VERBOSE_SCRIPT_LOGGING是否为空。-n string判断字符串是否非空
  #[[是 bash 程序语言的关键字。用于判断
  if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
    #作为一个字符串输出所有参数。使用时加引号"$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数
      if [[ -n "$TTY" ]]; then
          echo "♦ $@" 1>$TTY
      else
          echo "♦ $*"
      fi
      echo "------------------------------------------------------------------------------" 1>$TTY
  fi
  #与$*相同。但是使用时加引号,并在引号中返回每个参数。"$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数
  if [[ -n "$TTY" ]]; then
      echo `$@ &>$TTY`
  else
      "$@"
  fi
  #显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
  return $?
}

EchoError() {
    #在shell脚本中,默认情况下,总是有三个文件处于打开状态,标准输入(键盘输入)、标准输出(输出到屏幕)、标准错误(也是输出到屏幕),它们分别对应的文件描述符是0,1,2
    # >  默认为标准输出重定向,与 1> 相同
    # 2>&1  意思是把 标准错误输出 重定向到 标准输出.
    # &>file  意思是把标准输出 和 标准错误输出 都重定向到文件file中
    # 1>&2 将标准输出重定向到标准错误输出。实际上就是打印所有参数已标准错误格式
    if [[ -n "$TTY" ]]; then
        echo "$@" 1>&2>$TTY
    else
        echo "$@" 1>&2
    fi
    
}

RunCMDToTTY() {
    if [[ ! -e "$TTY" ]]; then
        EchoError "=========================================="
        EchoError "ERROR: Not Config tty to output."
        exit -1
    fi
    
    if [[ -n "$CMD" ]]; then
        RunCommand "$CMD" ${CMD_FLAG}
    else
        EchoError "=========================================="
        EchoError "ERROR:Failed to run CMD. THE CMD must not null"
    fi
}


RunCMDToTTY

符号的可见性

符号可见性

-O1 -Oz 生成目标文件时 在编译阶段的处理

dead code strip 死代码剥离 在链接阶段处理

strip 剥离符号 对mach-O修改 (deployment postprocessing 设置为yes)

xcode设置strip

1 找到下面的设置组

Build Settings -> Deployment

2 找到下面的设置项,设置为YES

Deployment PostProcessing 设置为 YES

3 找到下面的设置项进行配置

Strip style 的类型选择:
All Symbols
Non-Global Symbols
Debugging Symbols

0203 链接与符号表

一. GCC四步详解

源码 到 可执行文件 的过程
预处理 -> 编译 -> 汇编 -> 链接

第一步:预处理(也叫预编译)
gcc -E  hello.c  -o hello.i
或者 cpp hello.c > hello.i 【cpp是预编译器】
将所有#define删除,并且展开所有的宏定义
处理所有的条件预编译指令,如#if #ifdef  #undef  #ifndef  #endif #elif
处理#include,将包含的文件插入到此处,这是一个递归的过程
删除所有注释   //   /* */
添加行号和文件名标识,以便于编译时产生的错误警告能显示行号
保留#pragma编译器指令


第二步:编译
gcc  -S  hello.i   -o  hello.s
将预处理完的.i文件进行一系列的词法分析、语法分析、语义分析及优
化后生成响应的汇编代码文件,这是整个程序构建的最核心的部分,也是最复杂的部分


第三步:汇编
gcc  -c  hello.s  -o  hello.o或者 as  hello.s -o  hello.o
汇编是将第二步生成的汇编代码编程机器可执行的指令,每一个汇编语句几乎都对应一条机器指令


第四步:链接
链接动态库和静态库

二. 目标文件

目标文件就是源代码经过编译后但未进行链接的那些中间文件


.o目标文件的地址并未虚拟化
.o文件内用到的其它库文件(macho)内的API比如NSLog,会将符号放入重定位符号表
.o文件内用到的其它.o文件内的API,同样会将符号放入重定位符号表
重定位符号表 内放置的是当前 .m/.o 用到的API(.o需要链接,才能变为可执行文件)


查看重定位符号表
通过查看重定位符号表,就可以知道当前.o文件调用了哪些api.
objdump --macho -reloc xx.o文件


多个.o文件 -> 链接 -> 可执行文件
重定位符号表  合并到 一张表中
总结:链接的过程就是处理.o文件中符号的过程

0204 全局和本地符号

一 全局符号与本地符号

全局变量在符号表里全都是全局符号 g:代表全局符号 l:代表本地符号

查看可执行文件的符号表

objdump --macho -syms MachOAndSymbol2 
MachOAndSymbol2:
SYMBOL TABLE:
0000000100008008 l     O __DATA,__data __dyld_private
0000000100008010 l     O __DATA,__data _global_init_value
0000000100008014 l     O __DATA,__data _static_init_value
0000000100008018 l     O __DATA,__bss _static_uninit_value
0000000100008028 l     O __DATA,__common _default_x
0000000100000000 g     F __TEXT,__text __mh_execute_header
0000000100008020 g     O __DATA,__common _global_uninit_value
0000000100003f50 g     F __TEXT,__text _main
0000000000000000         *UND* _NSLog
0000000000000000         *UND* ___CFConstantStringClassReference
0000000000000000         *UND* dyld_stub_binder

二 全局符号变为本地符号

1 全局符号
全局符号:对当前项目可见对其它项目也可见(对内对外都可见).
比如库A内有一个方法,但并未在头文件内暴露
void global_object () {
  NSLog(@"global_object");
}

在app内引入库A,只声明一个void global_object ()方法并不写实现.
当调用global_object ()时,就会调用到库A内的方法.


2 全局符号变为本地符号

#方式一:使用属性修饰
double default_x __attribute__((visibility("hidden"))) ;

#方式二:static修饰
static int static_init_value = 9;



3 static只对定义它的文件可见,是本地符号
static void global_object () {
  NSLog(@"global_object");
}


4 two_levelnamespace & flat_namespace:
二级命名空间与一级命名空间。
链接器默认采用二级命名空间,也就是除了会记录符号 名称,还会记录符号属于哪个Mach-O的,
比如会记录下来_NSLog来自Foundation。

0205 同一文件相同全局符号

同一个mach-o文件内有相同的全局符号,会报错.

0206 导入导出符号

一 查看导出符号

我们的APP使用Foundation框架下的NSLog
NSLog对我们的APP来说导入了NSLog
NSLog对Foundation框架来说导出了NSLog
全局符号默认是导出符号,但我们可以通过链接器控制它

查看可执行文件的导出符号

objdump --macho --exports-trie MachOAndSymbol3
MachOAndSymbol3:
Exports trie:
0x100000000  __mh_execute_header
0x100003F50  _main
0x100008014  _lxy_global_value
0x100008020  _global_uninit_value

二 间接符号表

动态库 -> 对外提供符号
间接符号表 -> 存储动态库符号

动态库全局符号 -> 变为导出符号 -> 使用动态库的APP的间接符号表
所以动态库的全局符号不可以通过 strip 脱掉

查看间接符号表

objdump --macho --indirect-symbols MachOAndSymbol3
MachOAndSymbol3:
Indirect symbols for (__TEXT,__stubs) 1 entries
address            index name
0x0000000100003f90     9 _NSLog
Indirect symbols for (__DATA_CONST,__got) 1 entries
address            index name
0x0000000100004000    11 dyld_stub_binder
Indirect symbols for (__DATA,__la_symbol_ptr) 1 entries
address            index name
0x0000000100008000     9 _NSLog

三. 将导出符号变为非导出符号

OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbol -Xlinker _OBJC_METACLASS_$_LGOneObject

因为OC的动态性,OC符号不能使用 hidden

将全局符号变为了本地符号,这个时候就可以脱去该符号,减少macho文件的体积

weak符号表

一 -map参数的使用

-map map_file_path
            Writes a map file to the specified path which details all symbols and their addresses in the output image.

二 weak symbol

1 弱引用符号

__attribute__((weak_import))

OTHER_LDFLAGS=$(inherited) -Xlinker -U -Xlinker _weak_import_function

//API
//弱引用:找不到符号就返回0
//没有找到符号不调用,找到符号就调用
if (weak_import_function) {
    weak_function();
}

//可以把整个动态库声明为弱引用,当找不到这个库时就不会报错了.


在终端
man ld 
然后查找 -S 或者 -U 代表的具体含义

2 弱定义符号

__attribute__((weak))
找到一个符号就会忽略其它的符号

对于一个全局符号不会影响符号是全局符号的特性(导出符号)

0208 Swift符号

1 re-export

OTHER_LDFLAGS=$(inherited) -Xlinker -alias -Xlinker _NSLog -Xlinker _Cat_log

[re-export] _Cat_log (_NSLog from Foundation)


2 swift符号
swift是静态语言

0209 strip

一 app中哪些符号可以脱掉

1 为什么符号能脱掉个人理解(猜测并非官方):
可执行文件在运行的时候,比如调用一个方法,是通过 call + 地址 这种方式.
应该首先区分符号跟符号表,符号起到的是一个占位的作用,符号信息存储在符号表.
在链接阶段会根据符号表找到真正要调用的地址,并替换.
到可执行文件这个时候符号表内有的符号已经没有用处了.
但这部分符号还在可执行文件内存储并占用包大小,所以可以脱掉,并减少包大小.


2 可以脱掉的符号
本地符号可以脱掉
APP中的全局符号可以脱掉,APP中的全局符号一般不需要导出
APP中的弱定义符号可以脱掉
间接符号表中的不能脱掉,间接符号表存储的是导入符号,不可以脱掉


3 静态库
静态库的本质是:.o合集 + 重定位符号表


4 xcode中符号类型
Debugging Symbols(.o静态库/可执行文件 动态库)
All Symbols 
Non-Global Symbols

二 思考

就符号的性质来说,使用静态库体积小还是动态库体积小?

静态库的本质是.o目标文件的合集,链接之后,符号就可以脱掉.     
动态库中的导出符号存储在APP的间接符号表中,不可以脱掉.     
所以从符号的性质来说,使用静态库的体积小. 

三 dead code strip

在链接阶段起作用
-dead_strip 官方解释:删除入口点或导出符号无法访问的函数和数据。
Remove functions and data that are unreachable by the entry point or exported symbols.

0210 llvm-strip

1 strip    
strip 是在修改macho中的一个内容

 
2 llvm-strip的使用


3 断点信息

br read -f 文件

br list strip

br enable strip

br write -f 文件

行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.