背景
- macOS 上客户端程序可能存在异常(高 CPU,高内存,死循环)
- 能够接触到现场,但是不能长时间占用(客户的电脑)
- 客户电脑没有任何开发工具
解决方案
- 抓 sample 并保存到文件
- 使用 Activity Monitor,双击进程,取样,存储
-
sample
命令
- 配合符号,分析 sample 文件,还原当时的调用栈
-
atos
命令解析对应地址在符号文件(dSYM)的函数名 -
dwarfdump
命令分析符号的 uuid 并匹配 sample 文件
-
- 调查代码,还原问题
简化方案
- 我写了一个
sample_parser
用来简化这个操作 - 简单的
sample_parser [dSYM] [Sample.log]
调用,输出函数栈 - 使用强大的
bash shell
编写(小工具编写使用shell
情有独钟)
sample_parser
源码
这里我就直接贴源码了,使用前提是确保安装了
Xcode
以及其CLI
工具集
#!/bin/bash
WORK_DIR="$(mktemp)_dir"
WORK_LIST="${WORK_DIR}/list"
WORK_ADDR="${WORK_DIR}/addr"
function log_info {
NOW=`date`
echo -e "\033[42;37m [${NOW}] [LOG] ${*} \033[0m" > /dev/stderr
}
function log_warning {
NOW=`date`
echo -e "\033[43;37m [${NOW}] [LOG] ${*} \033[0m" > /dev/stderr
}
function log_error {
NOW=`date`
echo -e "\033[41;37m [${NOW}] [ERR] ${*} \033[0m" > /dev/stderr
}
function sample_help {
cat << EOF
Usage: ${0} {dsym_file_path} {sample_log_path}
dsym_file_path: dSYM symbol file path
sample_log_path: sample.log file path
EOF
}
function get_address_list {
local IFS=$'\n'
local base_addr="${1}"
local dsym="${2}"
local sample="${3}"
log_info 'Generating ...'
for line in $(cat "${sample}");
do
if [ "$(echo $line | grep "${base_addr}" | xargs)" != "" ];
then
addr=$(echo "$line" | egrep -E "\[(\w*)\]" -o | sed 's/^\[\(.*\)\]$/\1/')
echo ${addr} >> ${WORK_LIST}
fi
done
}
function run_atos {
local base_addr="${1}"
local dsym="${2}"
log_info 'Parseing ...'
atos -o "${dsym}" -arch x86_64 -l "${base_addr}" < ${WORK_LIST} > ${WORK_ADDR}
}
function parse_address {
local IFS=$'\n'
local base_addr="${1}"
local dsym="${2}"
local sample="${3}"
local number=1
log_info 'Formatting ...'
for line in $(cat "${sample}");
do
if [ "$(echo $line | grep "${base_addr}" | xargs)" != "" ];
then
local addr=$(echo "$line" | egrep -E "\[(\w*)\]" -o | sed 's/^\[\(.*\)\]$/\1/')
echo -n "Parse addr(${addr}): "
sed -n "${number}p" ${WORK_ADDR}
number=$((number + 1))
else
echo "$line" | xargs
fi
done
}
function get_dsym_uuid {
dsym="$1"
xcrun dwarfdump --uuid "${dsym}" | awk '{print $2}'
}
function get_base_addr_by_uuid {
uuid="$1"
sample="$2"
cat "${sample}" | grep "${uuid}" | awk '{print $1}'
}
function main {
local dsym="$1"
local sample="$2"
if [ "$dsym" == "" ] || [ "$sample" == "" ];
then
log_error "Need params !"
sample_help
return
fi
local uuid="$(get_dsym_uuid "${dsym}")"
if [ "${uuid}" == "" ];
then
log_error "Error get uuid !"
return
fi
local base_addr="$(get_base_addr_by_uuid "${uuid}" "${sample}")"
if [ "${base_addr}" == "" ];
then
log_error "Error get base addr !"
return
fi
log_info "Base address is ${base_addr} ..."
mkdir -p ${WORK_DIR}
get_address_list "${base_addr}" "${dsym}" "${sample}"
run_atos "${base_addr}" "${dsym}"
parse_address "${base_addr}" "${dsym}" "${sample}"
}
main "$@"