Mac调试工具LLDB和Frida

LLDB

// 运行app
lldb /Users/mac/Desktop/MacTest.app
lldb /Users/mac/Desktop/confuse_mac.app
// 2个打印的地址相差0x100000000 调试mac应用好像没用,直接用IDA Hopper显示的地址就行
// IDA官网的免费版本不支持arm (下载windows破解wine打包的网上IDA Pro 8.3版本)导致之前看到的函数地址和Hopper不一样
image list 打印的基地址: 0x102a94000
image list -o -f打印的地址: 0x002a94000 
// 直接打印第1个镜像
po _dyld_get_image_vmaddr_slide(0)
// 默认是没运行 r运行
r

// 暂停
ctrl + c

// 继续运行
c

// 断点 -a的0x可以省略
b -[ViewController getString:]
b +[ViewController hello]
b [AFZLanguageDecemberSplints lowFileElectrodes:]
br s -a 100002e84
br s -a  0x100000000+0x100017458

// 条件断点
breakpoint set --name lowFileElectrodes:
breakpoint modify -c '(BOOL)objc_msgSend((id)$x2, @selector(isEqualToString:), @"token")'
breakpoint modify -c '(BOOL)objc_msgSend((id)$x2, @selector(containsString:), @"设备%d名称:%@ 到期时间:%@,允许版本:%@")'

// 列出所有断点
br list

// 删除断点 指定br list列出的序号可以删除
br delete 2

// 打印堆栈
bt

// frame select堆栈ID
// 展示当前作用域下的参数和局部变量:frame variable
//反汇编地址: dis -s +地址  当我们调试过程中遇到crash,我们可以通过:dis -s +地址 命令来反汇编地址,来排查导致crash的具体原因。

// 写入内存指令临时覆盖掉逻辑
register read x0
register write x0 0
po (char *)0x000060000374ca80
po $x0
// 每次断点都执行一下po $x0
target stop-hook add -o "po $x0"

hread step-over 、 next 、n 单步运行,把子函数当做整体一步执行
thread step-in 、step、 s 单步运行,遇到子函数会进入子函数
thread step-inst-over、 nexti、 ni 单步运行,把子函数当做整体一步执行
thread step-inst、 stepi 、si 单步运行,遇到子函数会进入子函数
thread step-out 、finish 直接执行完当前函数的所有代码,返回到上一个函数(遇到断点会卡

LLDB自定义打印方法和Block参数 脚本放到最后面

参考 自定义打印方法参数的LLDB命令lldb快速打印Objective-C方法中block参数的签名

  • 若是自动加载,需要vim ~/.lldbinit,然后里面就输入以下命令
command script import ~/custom.py
  • 若是临时加载,只需在LLDB模式下输入command script import ~/custom.py

// Debug是文件名,objargs_func是文件下边的方法名就是具体lldb参数实现,最后的objargs是lldb 环境敲得命令

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand('command script add -f Debug.objargs_func objargs')

LLDB插件Chisel

// Chisel是Facebook发布的一个lldb插件,能够帮助调试 
使用说明 https://www.jianshu.com/p/290e81b632e6
 brew install chisel
1. 找到fblldb.py:/usr/local/Cellar/chisel/版本号/libexec/fblldb.py
2. 找到~/.lldbinit文件,然后进行编辑,在末尾添加
command script import /usr/local/Cellar/chisel/1.8.1/libexec/fblldb.py

搜索字符串

// 查找文件中的字符串  查找指定目录及其子目录中的所有文件,并检查每个文件是否包含字符串 "用户到期",如果包含,则输出文件名
find /Users/mac/Desktop/confuse_mac.app/Contents -type f -exec sh -c 'strings "$1" | grep "用户到期" && echo "$1"' sh {} \;
// 查找文件 查找当前目录及其子目录中所有名字叫做 CoreRepairCore.framework 的文件或目录 将标准错误输出(文件描述符 2)重定向到 /dev/null,从而忽略并不打印权限不足的错误信息。
find ./ -name "CoreRepairCore.framework" 2>/dev/null
// 从指定目录中查找所有不是 .DS_Store 的文件,并将它们复制到 ./11 目录下
find ./ -d -type f -not -name .DS_Store -exec cp {} ./11 \;

恢复符号表

原理 iOS符号表恢复&逆向支付宝

// 为了堆栈可以看到裁去符号表的方法名
// 原版的restore-symbol有问题 用https://github.com/HeiTanBc/restore-symbol 我自己也fork这个了 教程在readme有写
// 如果是FAT二进制 可以用lipo瘦身只要arm64 我的电脑M2 是arm64
lipo /Users/mac/Desktop/confuse_mac.app/Contents/MacOS/confuse_mac -thin arm64 -output confuse_mac_arm64

// restore-symbol clone的目录restore-symbol
./restore-symbol /Users/mac/Desktop/confuse_mac.app/Contents/MacOS/confuse_mac_arm64  -o /Users/mac/Desktop/confuse_mac.app/Contents/MacOS/confuse_mac_symbol
// 恢复符号表后要改名替换原来的confuse_mac二进制 然后confuse_mac.app无法打开的情况重新签名
sudo codesign --remove-signature "/Users/mac/Desktop/confuse_mac.app" && sudo codesign -f -s - --timestamp=none --all-architectures --deep "/Users/mac/Desktop/confuse_mac.app" && sudo xattr -cr "/Users/mac/Desktop/confuse_mac.app"

常用修改二进制

// return 0
6a 00 58 c3  
// nop
90 90

Frida

frida-trace "confuse_mac" -m "*[AFZUserSecretariesHappen *]"
frida-trace "confuse_mac" -m "*[AFZLanguageDecemberSplints lowFileElectrodes:]"
frida-trace "confuse_mac" -m "*[AFZNetworkSuppliesQualifica lowFileElectrodes:]" -o run.txt // 输出日志到
frida-trace "confuse_mac" -m "*[* armsWindlassAnchor:]" -o run.txt


frida-trace "confuse_mac" -m "*[AFZLanguageDecemberSplints lowFileElectrodes:]" -m "*[* objectForKeyedSubscript:]" -m "*[* objectForKeyedSubscript:]" -o run.txt

打印堆栈 在onEnter函数插入打印
log('\tBacktrace:\n\t' + Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n\t'));

var after = new ObjC.Object(retval); // 打印出来是个指针时,请用该方式转换后再打印
log(`after:${after}`);

方法调用时
onEnter(log, args, state) {
    var self = new ObjC.Object(args[0]);  // 当前对象
    var method = args[1].readUtf8String();  // 当前方法名
    log(`[${self.$className} ${method}]`);

    var isData = false;

    // 字符串
    // var str = ObjC.classes.NSString.stringWithString_("hi wit!")  // 对应的oc语法:NSString *str = [NSString stringWithString:@"hi with!"];
    // args[2] = str  // 修改入参

    // array
    // var 

    // 数组
    // var array = ObjC.classes.NSMutableArray.array();  // 对应的oc语法:NSMutableArray array = [NSMutablearray array];
    // array.addObject_("item1");  // 对应的oc语法:[array addObject:@"item1"];
    // array.addObject_("item2");  // 对应的oc语法:[array addObject:@"item2"];
    // args[2] = array; // 修改入参

    // 字典
    // var dictionary = ObjC.classes.NSMutableDictionary.dictionary(); // 对应的oc语法:NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
    // dictionary.setObject_forKey_("value1", "key1"); // 对应的oc语法:[dictionary setObject:@"value1" forKey:@"key1"]
    // dictionary.setObject_forKey_("value2", "key2"); // 对应的oc语法:[dictionary setObject:@"value2" forKey:@"key2"]
    // args[2] = dictionary; // 修改入参

    // 字节
    var data = ObjC.classes.NSMutableData.data(); // 对应的oc语法:NSMutableData *data = [NSMutableData data];
    var str = ObjC.classes.NSString.stringWithString_("hi wit!")  // 获取一个字符串。 对应的oc语法:NSString *str = [NSString stringWithString:@"hi with!"];
    var subData = str.dataUsingEncoding_(4);  // 将str转换为data,编码为utf-8。对应的oc语法:NSData *subData = [str dataUsingEncoding:NSUTF8StringEncoding];
    data.appendData_(subData);  // 将subData添加到data。对应的oc语法:[data appendData:subData];
    args[2] = data; // 修改入参
    isData = true;

    // 更多数据类型:https://developer.apple.com/documentation/foundation

    var before = args[2];

    // 注意,日志输出请直接使用log函数。不要使用console.log()
    if (isData) {
        // 打印byte对象
      var after = new ObjC.Object(args[2]); // 打印NSData
      var outValue = after.bytes().readUtf8String(after.length()) // 将data转换为string
      log(`before:=${before}=`);
      log(`after:=${outValue}=`);
    } else {
        // 打印字符串、数组、字段
      var after = new ObjC.Object(args[2]); // 打印出来是个指针时,请用该方式转换后再打印
      log(`before:=${before}=`);
      log(`after:=${after}=`);
    }

    // 如果是自定义对象时,使用以上方法无法打印时,请使用以下方法:
    // var customObj = new ObjC.Object(args[0]); // 自定义对象
    // // 打印该对象所有属性
    // var ivarList = customObj.$ivars;
    // for (key in ivarList) {
    //   log(`key${key}=${ivarList[key]}=`);
    // }

    // // 打印该对象所有方法
    // var methodList = customObj.$methods;
    // for (var i=0; i<methodList.length; i++) {
    //   log(`method=${methodList[i]}=`);
    // }
  },


方法返回时
onLeave(log, retval, state) {
    // 字符串
    var str = ObjC.classes.NSString.stringWithString_("hi wit!")  // 对应的oc语法:NSString *str = [NSString stringWithString:@"hi with!"];
    retval.replace(str)  // 修改返回值
    var after = new ObjC.Object(retval); // 打印出来是个指针时,请用该方式转换后再打印
    log(`before:=${retval}=`);
    log(`after:=${after}=`);

    // 其他数据类型,请往上看
  }

LLDB自定义脚本

# zlldb_block.py lldb快速打印Objective-C方法中block参数的签名
import lldb
import optparse
import shlex

# https://everettjf.github.io/2020/02/11/print-block-in-lldb/
# github.com:everettjf/zlldb.git
###### Init ###### 

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand('command script add -f zlldb_block.cmd_ztest ztest')
    debugger.HandleCommand('command script add -f zlldb_block.cmd_zdebug zdebug')
    debugger.HandleCommand('command script add -f zlldb_block.cmd_zdoc zdoc')

    debugger.HandleCommand('command script add -f zlldb_block.cmd_zpvc zpvc')
    debugger.HandleCommand('command script add -f zlldb_block.cmd_zpview zpview')

    debugger.HandleCommand('command script add -f zlldb_block.cmd_zp1 zp1')
    debugger.HandleCommand('command script add -f zlldb_block.cmd_zp2 zp2')
    debugger.HandleCommand('command script add -f zlldb_block.cmd_zp3 zp3')
    debugger.HandleCommand('command script add -f zlldb_block.cmd_zp4 zp4')
    debugger.HandleCommand('command script add -f zlldb_block.cmd_zp5 zp5')

    debugger.HandleCommand('command script add -f zlldb_block.cmd_zmemory zmemory')
    debugger.HandleCommand('command script add -f zlldb_block.cmd_zdis zdis')
    debugger.HandleCommand('command script add -f zlldb_block.cmd_zblock zblock')

    print('zlldb loaded')

###### Dev Help ###### 

def cmd_ztest(debugger, command, result, internal_dict):
    print('zlldb test')

def cmd_zdebug(debugger, command, result, internal_dict):
    import pdb; pdb.set_trace()
    print('zdebug')

def cmd_zdoc(debugger, command, result, internal_dict):
    import os
    os.system("open https://lldb.llvm.org/python_reference/lldb.{}-class.html".format(command))

###### Util ###### 

def exec_expression(interpreter, expression, print_when_noresult=None):
    res = lldb.SBCommandReturnObject()
    interpreter.HandleCommand(expression, res)
    if res.HasResult():
        print(res.GetOutput())
    else:
        if print_when_noresult is not None:
            print(print_when_noresult)

###### View / ViewController Print ###### 

def cmd_zpvc(debugger, command, result, internal_dict):
    # expression -lobjc -O -- [UIViewController _printHierarchy]
    res = lldb.SBCommandReturnObject()
    interpreter = debugger.GetCommandInterpreter()
    expression = 'expression -lobjc -O -- [UIViewController _printHierarchy]'
    interpreter.HandleCommand(expression, res)
    if res.HasResult():
        print(res.GetOutput())
    else:
        print('No result')

def cmd_zpview(debugger, command, result, internal_dict):
    # expression -lobjc -O -- [(id)[[UIApplication sharedApplication] keyWindow] recursiveDescription]
    res = lldb.SBCommandReturnObject()
    interpreter = debugger.GetCommandInterpreter()
    expression = 'expression -lobjc -O -- [(id)[[UIApplication sharedApplication] keyWindow] recursiveDescription]'
    interpreter.HandleCommand(expression, res)
    if res.HasResult():
        print(res.GetOutput())
    else:
        print('No result')

###### Parameter Print ###### 


def cmd_zp1(debugger, command, result, internal_dict):
    interpreter = debugger.GetCommandInterpreter()
    exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg1', 'no result for arg1')

def cmd_zp2(debugger, command, result, internal_dict):
    interpreter = debugger.GetCommandInterpreter()
    exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg1', 'no result for arg1')
    exec_expression(interpreter, 'expression -O -- (char*)$arg2', 'no result for arg2')

def cmd_zp3(debugger, command, result, internal_dict):
    interpreter = debugger.GetCommandInterpreter()
    exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg1', 'no result for arg1')
    exec_expression(interpreter, 'expression -O -- (char*)$arg2', 'no result for arg2')
    exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg3', 'no result for arg3')

def cmd_zp4(debugger, command, result, internal_dict):
    interpreter = debugger.GetCommandInterpreter()
    exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg1', 'no result for arg1')
    exec_expression(interpreter, 'expression -O -- (char*)$arg2', 'no result for arg2')
    exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg3', 'no result for arg3')
    exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg4', 'no result for arg4')

def cmd_zp5(debugger, command, result, internal_dict):
    interpreter = debugger.GetCommandInterpreter()
    exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg1', 'no result for arg1')
    exec_expression(interpreter, 'expression -O -- (char*)$arg2', 'no result for arg2')
    exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg3', 'no result for arg3')
    exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg4', 'no result for arg4')
    exec_expression(interpreter, 'expression -lobjc -O -- (id)$arg5', 'no result for arg5')

###### Memory Print ######

def cmd_zmemory(debugger, command, result, internal_dict):
    cmd_args = shlex.split(command)

    usage = "usage: %prog <address> <size=8>"
    parser = optparse.OptionParser(prog='zmemory', usage=usage)
    
    try:
        (options, args) = parser.parse_args(cmd_args)
    except:
        print("error parse parameter")
        return
    
    if len(args) == 0:
        print("You need to specify the name of a variable or an address")
        return

    address = int(args[0],0)
    size = 8
    if len(args) == 2:
        size = int(args[1],0)

    print("address: 0x%x" % (address))
    print("size: 0x%x" % (size))

    interpreter = debugger.GetCommandInterpreter()
    exec_expression(interpreter, 'memory read --size %d --format x 0x%x' % (size, address), 'no result')
    

###### Disassemble ######

def cmd_zdis(debugger, command, result, internal_dict):
    cmd_args = shlex.split(command)

    usage = "usage: %prog <address> <size=8>"
    parser = optparse.OptionParser(prog='zmemory', usage=usage)
    
    try:
        (options, args) = parser.parse_args(cmd_args)
    except:
        print("error parse parameter")
        return
    
    if len(args) == 0:
        print("You need to specify the name of a variable or an address")
        return

    address = int(args[0],0)
    instruction_count = 20 
    if len(args) == 2:
        instruction_count = int(args[1],0)

    print("address: 0x%x" % (address))
    print("instruction_count: %d" % (instruction_count))

    interpreter = debugger.GetCommandInterpreter()
    disass_cmd = "disassemble --start-address 0x%x -c %d" %(address, instruction_count)
    exec_expression(interpreter, disass_cmd, 'no result')
    

###### Block ###### 

'''
struct Block_literal_1 {
    void *isa;
    int flags;
    int reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 {
        unsigned long int reserved;
        unsigned long int size;
        void (*copy_helper)(void *dst, void *src);
        void (*dispose_helper)(void *src);
        const char *signature;
    } *descriptor;
};
'''
def zblock_print_block_signature(debugger, target, process, block_address):
    pointer_size = 8 if zblock_arch_for_target_is_64bit(target) else 4
    # print("pointer size = {0}".format(pointer_size))
    # print("block address = %x"%(block_address))

    flags_address = block_address + pointer_size    # The `flags` integer is after a pointer in the struct
    
    flags_error = lldb.SBError()
    flags = process.ReadUnsignedFromMemory(flags_address, 4, flags_error)

    if not flags_error.Success():
        print("Could not retrieve the block flags")
        return
    
    block_has_signature = ((flags & (1 << 30)) != 0)    # BLOCK_HAS_SIGNATURE = (1 << 30)
    block_has_copy_dispose_helpers = ((flags & (1 << 25)) != 0) # BLOCK_HAS_COPY_DISPOSE = (1 << 25)

    
    if not block_has_signature:
        print("The block does not have a signature")
        return
    
    block_descriptor_address = block_address + 2 * 4 + 2 * pointer_size # The block descriptor struct pointer is after 2 pointers and 2 int in the struct
    
    block_descriptor_error = lldb.SBError()
    block_descriptor = process.ReadPointerFromMemory(block_descriptor_address, block_descriptor_error)
    if not block_descriptor_error.Success():
        print("Could not read the block descriptor struct")
        return
    
    signature_address = block_descriptor + 2 * pointer_size # The signature is after 2 unsigned int in the descriptor struct
    if block_has_copy_dispose_helpers:
        signature_address += 2 * pointer_size   # If there are a copy and dispose function pointers the signature
    
    signature_pointer_error = lldb.SBError()
    signature_pointer = process.ReadPointerFromMemory(signature_address, signature_pointer_error)
    
    signature_error = lldb.SBError()
    signature = process.ReadCStringFromMemory(signature_pointer, 255, signature_error)

    if not signature_error.Success():
        print("Could not retrieve the signature")
        return
    
    print("Signature Address: 0x%x" %(signature_address))
    print("Signature String: %s" %(signature))

    escaped_signature = signature.replace('"', '\\"')

    method_signature_cmd = 'po [NSMethodSignature signatureWithObjCTypes:"' + escaped_signature + '"]'
    debugger.HandleCommand(method_signature_cmd)

    docurl = 'https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html'
    print('Type Encodings Ref: %s' % (docurl))

def zblock_disass_block_invoke_function(debugger, target, process, block_address, instruction_count):
    pointer_size = 8 if zblock_arch_for_target_is_64bit(target) else 4
    
    invoke_function_address = block_address + pointer_size + 2 * 4  # The `invoke` function is after one pointer and 2 int in the struct
    print("Invoke address: 0x%x" % (invoke_function_address))
    
    invoke_function_error = lldb.SBError()
    invoke_function_pointer = process.ReadPointerFromMemory(invoke_function_address, invoke_function_error)
    if not invoke_function_error.Success():
        print("Could not retrieve the block invoke function pointer")
        return
    
    disass_cmd = "disassemble --start-address " + str(invoke_function_pointer) + " -c " + str(instruction_count)
    debugger.HandleCommand(disass_cmd)

def zblock_arch_for_target_is_64bit(target):
    # like: x86_64h, x86_64
    arch_64 = ['arm64', 'x86_64']
    arch = target.GetTriple().split('-')[0]
    for arch64_item in arch_64:
        if arch in arch64_item:
            return True
    return False

def cmd_zblock(debugger, command, result, internal_dict):
    cmd_args = shlex.split(command)

    usage = "usage: %prog arg1 [--disass -d] [--number-instructions -n]"
    parser = optparse.OptionParser(prog='zblock', usage=usage)
    parser.add_option('-d', '--disass', action='store_true', dest='disass', default=False)
    parser.add_option('-n', '--number-instructions', dest='numberinstructions', default=20)
    
    try:
        (options, args) = parser.parse_args(cmd_args)
    except:
        print("error parse parameter")
        return
    
    if len(args) == 0:
        print("You need to specify the name of a variable or an address")
        return
    
    number_instructions = options.numberinstructions
    should_disass = options.disass
    
    target = debugger.GetSelectedTarget()
    process = target.GetProcess()
    thread = process.GetSelectedThread()
    frame = thread.GetSelectedFrame()

    variable_arg = args[0]
    address = int(variable_arg,0)
    if address == 0: 
        print("invalid address")
        return

    print("Block address: 0x%x" % (address))
    
    zblock_print_block_signature(debugger, target, process, address)

    if should_disass:
        zblock_disass_block_invoke_function(debugger, target, process, address, number_instructions)

# lldb_debug_extension.py  自定义打印方法参数的LLDB命令
import lldb
import re

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand('command script add -f lldb_debug_extension.objargs_func args')

# 格式化输出
def __format_output(return_obj):
   output = return_obj.GetOutput()
   return None if not output else output.replace('\n', '').replace('\r', '')

# 打印各个参数
def __print_arg(interpreter, return_obj, sequence):
    """
    打印函数的第几个参数,传入1打印第一个,以此类推
    """
    register = '$x%s' % str(sequence + 1)
    # 参数值
    interpreter.HandleCommand('po %s' % register, return_obj)
    arg_value = __format_output(return_obj)
    # 参数类型
    interpreter.HandleCommand('po [%s class]' % register, return_obj)
    arg_type: str = __format_output(return_obj)
    # 不是oc对象类型,是基本数据类型
    if not arg_type:
        interpreter.HandleCommand('p %s' % register, return_obj)
        p1 = re.compile(r'[(](.*?)[)]', re.S)  # 提取括号里面的参数类型
        arr = re.findall(p1, __format_output(return_obj))
        arg_type = arr[0]
        print("第%s个参数:【类型:%s】【值:%s】" % (sequence, arg_type, arg_value))
        return
    # 是oc对象类型,判断是不是block类型
    interpreter.HandleCommand('po (BOOL)[%s isKindOfClass: [NSBlock class]]' % register, return_obj)
    is_block = __format_output(return_obj)
    if not is_block == 'YES':  # 如果不是block
        print("第%s个参数:【类型:%s】【值:%s】" % (sequence, arg_type, arg_value))
        return
    # block的处理方式
    interpreter.HandleCommand('x/4xg %s' % register, return_obj)
    descriptor_address = __format_output(return_obj).split(' ')[-1]
    offset = 3 if 'Global' in arg_type else 5
    interpreter.HandleCommand('x/%sxg %s' % (str(offset),descriptor_address), return_obj)
    sign_address = __format_output(return_obj).split(' ')[-1]
    interpreter.HandleCommand('x/s %s' % sign_address, return_obj)
    sign = __format_output(return_obj).split(' ')[1]
    print("第%s个参数:【类型:%s】【签名:%s】" % (sequence, arg_type, sign))


# 定义打印参数的函数
def objargs_func(debugger, command, exe_ctx, result, internal_dict):
    """
    人性化的方式打印objc_msgSend的各个参数
    """
    interpreter = debugger.GetCommandInterpreter()
    # 保存结果
    return_obj = lldb.SBCommandReturnObject()
    # 处理命令
    interpreter.HandleCommand('po $x0', return_obj)
    print("方法调用者:%s" % __format_output(return_obj))
    interpreter.HandleCommand('x/s $x1', return_obj)
    method_name: str = __format_output(return_obj).split(' ')[1][1:-1]
    print("方法名:\t    %s" % method_name)
    args = method_name.split(':')[:-1]
    if not len(args):  # 如果没有参数
        return
    for index, arg in enumerate(args):
        sequence = index + 1
        __print_arg(interpreter, return_obj, sequence)


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容