• Lua中如何实现类似gdb的断点调试03通用变量修改及调用栈回溯


    在前面两篇01最小实现02通用变量打印中,我们已经实现了设置断点、删除断点及通用变量打印接口。

    本篇将继续新增两个辅助的调试接口:调用栈回溯打印接口、通用变量设置接口。前者打印调用栈的回溯信息,后者可以方便地修改变量的值,支持局部变量、upvalue以及全局的_ENV中的变量。

    本文代码已开源至Github,欢迎watch/star。

    本博客已迁移至CatBro's Blog,那里是我自己搭建的个人博客,欢迎关注。

    调用栈打印函数

    我们首先来实现调用栈回溯打印接口printtraceback(),这个接口比较简单,只是简单地包装了一下debug.traceback(),对层级进行了一个修正,就不多介绍了。

    -- 打印调用栈的一个回溯
    local function printtraceback(level)
        -- 层次的默认值为1
        -- 加上4是为了修正层次数以包含printtraceback, debug mainchunk, debug.debug, hook
        level = (level or 1) + 4
        print(debug.traceback(nil, level))
    end
    

    通用变量值修改函数

    接着来实现通用的变量值修改函数_setvarvalue(),这个函数的结构跟_getvarvalue()类似,依次在局部变量、上值和_ENV表中查找,只不过找到之后会修改相应的值。函数有三个参数,name为要修改的变量的名字,value是修改的目标值,level指示在哪个层级的函数中查找,我们同样分成几部分来看。

    local function _setvarvalue (name, value, level)
        -- 省略
    end
    

    局部变量中查找

    同样地,先处理层级,层级的默认值为1, 将层级加上1是为了将层次修正为包含_setvavalue函数自己。

    然后遍历局部变量表,查找是否有名字为name的变量,如果找到的话记录其索引。注意 我们找到之后并没有立马跳出循环,因为可能具有多个同名的局部变量,我们应该获取索引最大的那个。

    循环结束之后,如果已经在局部变量中找到了name,就修改该变量的值为value,然后返回"local",指示修改的是局部变量。

    local function _setvarvalue (name, value, level)
        local index
    
        -- 加1是为了将层次修正了包含_setvarvalue自身
        level = (level or 1) + 1
        -- 在局部变量中查找
        for i = 1, math.huge do
            local n, v = debug.getlocal(level, i)
            if not n then break end
            if n == name then
                index = i
                -- 只更新索引值,并不退出循环
            end
        end
        if index then
            debug.setlocal(level, index, value)
            return "local"
        end
        -- 省略
    end
    

    上值中查找

    如果在局部变量中没有找到,我们再尝试到upvalue中进行查找。首先通过getbug.getinfo获取到第level层的函数,然后遍历其上值,如果找到匹配的变量就修改其值为value,然后返回"upvalue"以指示修改的是上值。

    local function _setvarvalue (name, value, level)
        -- 省略
        local func = debug.getinfo(level, "f").func
        for i = 1, math.huge do
            local n, v = debug.getupvalue(func, i)
            if not n then break end
            if n == name then
                debug.setupvalue(func, i, value)
                return "upvalue"
            end
        end
        -- 省略
    end
    

    _ENV表中查找

    如果在普通的上值中还是没有找到,我们就去_ENV表中查找。首先调用_getvarvalue获取到_ENV表,注意这里的isenv标志为true。如果如果找到了_ENV表且表中存在名为name的变量,就修改其值为value,然后返回"global"以指示是修改的_ENV表中的值。如果没有_ENV表或表中不存在要找的变量,就返回nil

    local function _setvarvalue (name, value, level)
        -- 省略
        local _, env = _getvarvalue("_ENV", level, true)
        if env and env[name] then
            env[name] = value
            return "global"
        else
            return nil
        end
    end
    

    包装函数

    接下来我们同样再定义一个包装函数,对层次数level进行修正以包含setvarvalue函数自身以及其上层的debug mainchunkdebug.debug以及钩子函数。

    然后对返回值进行检查,如果返回值为真,说明修改变量值成功,就打印变量类型,否则提示未找到。

    local function setvarvalue (name, value, level)
        -- level 默认值为1
        -- 加4是为了将层次纠正为包含 settvarvalue, debug mainchunk, debug.debug和hook
        level = (level or 1) + 4
        local where = _setvarvalue(name, value, level)
        if where then
            print(where, name)
        else
            print(name, "not found")
        end
    end
    

    接口定义好了,让我们把这两个接口也添加到返回到表中。

    return {
        -- 省略
        printtraceback = printtraceback,
        setvarvalue = setvarvalue,
    }
    

    测试脚本

    接下来编写一个测试脚本test.lua以对我们新添加的接口进行测试。脚本很简单,就不多做解释了。

    local ldb = require "luadebug"
    local setbp = ldb.setbreakpoint
    local rmbp = ldb.removebreakpoint
    pv = ldb.printvarvalue
    sv = ldb.setvarvalue
    ptb = ldb.printtraceback
    
    g = 1
    
    local u = 2
    local function foo (n)
        local a = 3
        u = u
        g = g
    end
    
    local id1 = setbp(foo, 14)
    
    foo(10)
    
    rmbp(id1)
    

    测试验证

    我们运行测试脚本,首先调用堆栈打印函数,默认是打印从断点所在函数开始的堆栈。

    $ lua test.lua
    (local)foo test.lua:14
    lua_debug> ptb()
    stack traceback:
    	test.lua:14: in local 'foo'
    	test.lua:21: in main chunk
    	[C]: in ?
    lua_debug>
    

    我们显式指定层数试一下。

    lua_debug> ptb(2)
    stack traceback:
    	test.lua:21: in main chunk
    	[C]: in ?
    lua_debug> ptb(0)
    stack traceback:
    	./luadebug.lua:20: in hook '?'
    	test.lua:14: in local 'foo'
    	test.lua:21: in main chunk
    	[C]: in ?
    lua_debug>
    

    没有问题,层数为2的时候少打印了一层,为0的时候则多打了一层。

    我们再来测试下变量值修改函数,先看来变量原来的值

    lua_debug> pv("a")
    local	3
    lua_debug> pv("u")
    upvalue	2
    lua_debug> pv("g")
    global	1
    lua_debug>
    

    然后修改变量的值,我们把这三个变量值都改成了6

    lua_debug> sv("a", 6)
    local	a
    lua_debug> sv("u", 6)
    upvalue	u
    lua_debug> sv("g", 6)
    global	g
    lua_debug>
    

    然后再打印下值检查下结果,可以看到都修改成功了。

    lua_debug> pv("a")
    local	6
    lua_debug> pv("u")
    upvalue	6
    lua_debug> pv("g")
    global	6
    lua_debug>
    

    我们再试试显式指定层级为2,将变量值再改为8

    lua_debug> sv("a", 8, 2)
    a	not found
    lua_debug> sv("u", 8, 2)
    local	u
    lua_debug> sv("g", 8, 2)
    global	g
    lua_debug> pv("a")
    local	6
    lua_debug> pv("u")
    upvalue	8
    lua_debug> pv("g")
    global	8
    lua_debug>
    

    变量a因为是函数foo的局部变量,所以外层看不到。变量u在main chunk中是属于局部变量,而变量g则还是全局变量。修改之后变量a的结果没有变,其他两个都改成了8。

    Well done!

  • 相关阅读:
    Docker 系列(四):Docker 容器数据卷简单使用
    【QML 动态对象】使用JS中的语句动态创建和销毁组件
    【QML 动态对象】Loader动态加载组件
    vue-cli2.0全局使用sass变量
    两边+居中 布局
    跳转子路由后左侧菜单跳转为空白页,路由地址出错
    el-tree可搜索单选
    el-tree固定高度加滚动条
    前端 权限控制 方式
    综合分析类
  • 原文地址:https://www.cnblogs.com/logchen/p/15990908.html
Copyright © 2020-2023  润新知