一、符号
1.符号结构体
Symbol Table
通过两个load commands:
LC_SYMTAB
:当前Mach-O中的符号表信息。LC_DYSYMTAB
:描述动态链接器使用其他的Symbol Table
信息。
用来描述 Symbol Table
的大小和位置,以及其他元数据。
LC_SYMTAB
用来描述该文件的符号表。不论是静态链接器还是动态链接器在此文件时,都要使用该load command。调试器也可以使用该load command找到调试信息。
symtab_command
定义LC_SYMTAB
价值命令具体属性。在usr/include/mach-o/loader.h中定义:
arduino
struct symtab_command {
// 共有属性。指明当前描述的加载命令,当前被设置为LC_SYMTAB
uint32_t cmd;
// 共有属性。指明加载命令的大小,当前被设置为sizeof(symtab_command)
uint32_t cmdsize;
// 表示从文件开始到symbol table所在位置的偏移量。symbol table用[nlist]来表示
uint32_t symoff;
// 符号表内符号的数量
uint32_t nsyms;
// 表示从文件开始到string table所在位置的偏移量。
uint32_t stroff;
// 表示string table大小(以byte为单位)
uint32_t strsize;
};
nlist
定义符号的具体表示含义:
arduino
struct nlist {
// 表示该符号在string table的索引
union {
// 在Mach-O中不使用此字段
char *n_name;
// 索引
long n_strx;
} n_un;
unsigned char n_type; /* type flag, see below */
unsigned char n_sect; /* section number or NO_SECT */
short n_desc; /* see <mach-o/stab.h> */
unsigned long n_value; /* value of this symbol (or stab offset) */
};
n_type
1字节通过,通过四位掩码保存数据:
-
N_STAB(0xe0)
:如果当前的n_type 包含这三位中任何一位,则该符号为(调试表符号stab)。在这种情况下,整个n_type整个字段将被解释为(stab value)。请参阅usr/include/mach-o/stab.h以获取有效的stab value。 -
N_PEXT(0x10)
:如果当前的n_type 包含此位。则将此符号标记为私有外部符号。(private_extern (visibility=hidden)) ,只在程序内可引用和访问。当前通过静态链接器链接的时候,不要将其转换成静态符号(可以通过 ld 的-keep_private_externs
关闭静态链接器的这种行为 )。 -
N_TYPE(0x0e)
:如果当前的n_type 包含此位。则使用预先定义的符号类型。 -
N_EXT(0x01)
:如果当前的n_type 包含此位。则符号为外部符号.该符号在该文件的外部定义或者在该文件中定义,但可以在其他文件中使用。
N_TYPE
字段的值包含:
N_UNDF(0x0)
:该符号未定义。未定义符号是在当前模块中引用,但是被定义在其他模块中的符号。n_sect
字段设置为NO_SECT
.N_ABS(0x2)
:该符号是绝对符号。链接器不会更改绝对符号的值。n_sect字段设置为NO_SECT.N_SECT(0xe)
:该符号在n_sect
中指定的段号中定义。N_PBUD(0xc)
:该符号未定义,镜像使用该符号的预绑定值。n_sect 字段设置为NO_SECT.N_INDR(0xa)
:该符号定义为与另一个符号相同。n_value字段是string table
中的索引,用于指定另一个符号的名称。链接该符号时,此符号和另一个符号都具有相同的定义类型和值。
nm命令
打印 nlist
结构的符号表(symbol table)
常用nm命令参数 | 说明 |
---|---|
nm | -pa a.o |
-a: | 显示符号表的所有内容 |
-g: | 显示全局符号 |
-p: | 不排序。显示符号表本来的顺序 |
-r: | 逆转顺序 |
-u: | 显示未定义符号 |
-m: | 显示N_SECT类型的符号(Mach-O符号)显示 |
实际用法: lldb:
2.macho修改
编译静态库sh
bash
SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
FILE_NAME=NYCat
echo "-----开始进入StaticLibrary"
# cd
pushd .
clang -x objective-c \
-target x86_64-apple-macos12.0 \
-fobjc-arc \
-isysroot $SYSROOT \
-c ${FILE_NAME}.m -o ${FILE_NAME}.o
ar -rc lib${FILE_NAME}.a ${FILE_NAME}.o
echo "-----开始退出StaticLibrary"
popd
zip --> .符号表 --> .a (全局符号) 开始编译静态库
OC符号冲突
在链接的时候找到对应的.a库,在工程配置Config.xcconfig
ini
// 1
HEADER_SEARCH_PATHS = ${SRCROOT}/lib
// 2. 在哪?.a .dylib
LIBRARY_SEARCH_PATHS = ${SRCROOT}/lib
// 3. 搜索那个静态库?lib .a .dylib
// ld --> linker --> 权限 --> 强制两个库
// 4. -ObjC
// 5. category --> ld --> 优化(静态库死代码剥离) Cocoapods
OTHER_LDFLAGS = -lNYCat -ObjC
编译运行: -ObjC是指明编译器把跟OC相关代码的都链接到库中。
在使用C++写的工具进行Mach-o修改注入
:
需要配置llvm环境(我在15款macpro配置成功,在M1 macmini配置失败。):
bash
OTHER_LDFLAGS = -Wl,-dead_strip -Wl,-search_paths_first -Wl,-headerpad_max_install_names -lm /Volumes/Tino/llvm/llvm-project/build/Debug/lib/libLLVMObject.a /Volumes/Tino/llvm/llvm-project/build/Debug/lib/libLLVMOption.a /Volumes/Tino/llvm/llvm-project/build/Debug/lib/libLLVMSupport.a /Volumes/Tino/llvm/llvm-project/build/Debug/lib/libLLVMMC.a /Volumes/Tino/llvm/llvm-project/build/Debug/lib/libLLVMBitReader.a /Volumes/Tino/llvm/llvm-project/build/Debug/lib/libLLVMCore.a /Volumes/Tino/llvm/llvm-project/build/Debug/lib/libLLVMRemarks.a /Volumes/Tino/llvm/llvm-project/build/Debug/lib/libLLVMBitstreamReader.a /Volumes/Tino/llvm/llvm-project/build/Debug/lib/libLLVMMCParser.a /Volumes/Tino/llvm/llvm-project/build/Debug/lib/libLLVMDebugInfoCodeView.a /Volumes/Tino/llvm/llvm-project/build/Debug/lib/libLLVMDebugInfoMSF.a /Volumes/Tino/llvm/llvm-project/build/Debug/lib/libLLVMTextAPI.a /Volumes/Tino/llvm/llvm-project/build/Debug/lib/libLLVMBinaryFormat.a /Volumes/Tino/llvm/llvm-project/build/Debug/lib/libLLVMSupport.a /Volumes/Tino/llvm/llvm-project/build/Debug/lib/libLLVMDemangle.a
配置完编译成功
核心代码展示(cat大佬写的):
ini
uint64_t PageSize;
switch (InMacho.getArch()) {
case Triple::ArchType::arm:
case Triple::ArchType::aarch64:
case Triple::ArchType::aarch64_32:
PageSize = 16384;
break;
default:
PageSize = 4096;
}
MachOReader Reader(InMacho);
Expected<std::unique_ptr<Object>> O = Reader.create();
Object &Obj = **O;
for (SymbolEntry &Sym : Obj.SymTable) {
// 1. outs()
outs() << Sym.Name << "\n";
// 2. Name
if (Sym.Name == "_OBJC_CLASS_$_NYCat") {
Sym.n_desc |= MachO::N_WEAK_DEF;
} else if (Sym.Name == "_OBJC_METACLASS_$_NYCat") {
Sym.n_desc |= MachO::N_WEAK_DEF;
}
}
Buffer &Out = FB;
MachOWriter Writer(**O, InMacho.is64Bit(), In.isLittleEndian(), PageSize, Out);
if (auto E = Writer.finalize()) {
E;
} else {
Writer.write();
}
outs c++内置打印工具,打印符号名称。名称等于_OBJC_CLASS__NYCat或者_OBJC_METACLASS__NYCat 。 设置n_desc 为N_WEAK_DEF 弱定义符号,修改后写入mach-o文件中。
arduino
//需要用下面命令 转成 .o文件
ar -x xxxx/libNYCat.a
成功修改mach-o YES!!!
3.分类与类查找
.a是.o的合集,基本上是全局符号,.a库有全局符号,local符号 调试符号,只能删除 调试符号。
如果是App呢? 全局符号 local符号 调试符号 [Foundation]导出符号 间接符号 [符号表],对于外部使用全局符号,其他符号都可以删除。
ini
HEADER_SEARCH_PATHS = ${SRCROOT}/lib
MACH_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/${PRODUCT_NAME}
//x --> macho 符号 结构体 16
//m --》 macho
CMD = objdump --macho --objc-meta-data ${MACH_PATH}
TTY=/dev/ttys000
//
OTHER_LDFLAGS = -ObjC
打印符号地址:
arduino
int main(int argc, const char * argv[]) {
[[LGStaticLibrary new] LGStaticLibraryCall];
return 0;
}
编译运行:
通过imp地址找到具体方法是实现。 我们通过分析这两个 exec 解析里面的符号。
scss
//1.第一步查看汇编代码
objdump --macho -d MachOAndSymbol
//2.第二部
16755+(%rip) 100003f4d = movq
//3.用lldb看下汇编地址
e -f x -- 0x100003f4d + 16755
//4.看地址
objdump --macho -s MachOAndSymbol
// 指定section
objdump --macho --section='__stubs' MachOAndSymbol
objdump --macho --section='__stub_helper' MachOAndSymbol
//打印间接符号,调试符号
objdump --macho --indirect-symbols -t MachOAndSymbol
指向了元类地址:
dyld -> 通过符号绑定 ,绑定后不同在绑定。