Thursday, May 9, 2013

Lua .natvis

I like Lua.  Sure, it has a few quirks, but it's pretty quick, and integrates nicely with C++.  Plus the source code compiled straight out of the box, even on Visual Studio 2012.

The main problem I ran into with Visual Studio 2012 is that Microsoft completely reworked the system for user formatting of variables, previously done with the autoexp.dat file.  The new system uses .natvis files, which is an XML file that lets you specify how your data is formatted.

Debugging Lua callbacks in C++ can be a bit difficult.  I put together some .natvis definitions for Lua 5.1 and 5.2 that helps to interpret the lua_State type, so you can see what is on the stack, and dig into tables, all from Visual Studio.


Lua 5.2 is not quite as straightforward as 5.1 - all of the Lua types have been packed in the unused bits of an otherwise invalid floating point number (QNAN) to save space.  This is also embedded inside an anonymous union that Visual Studio has a lot of trouble working with.  This results in a lot of bit masking to extract the data.

Because the internal formats are somewhat different, but the structures are named the same, you will need to use the matching .natvis definition for your version of Lua.  Put the XML below in a lua.natvis file in your "Documents\Visual Studio 2012\Visualizers" directory to use it for all projects.

Lua 5.1 (with old autoexp.dat types included as comments):
 <?xml version="1.0" encoding="utf-8"?>   
 <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">   
 <!--       
 lua_State {  
      preview (  
           #(  
                "thread top=", [$c.top-$c.base, i]  
           )  
      )  
      children (  
           #(  
                [raw members]: [$c,!],  
                stack size: [$c.top-$c.base, i],  
                globals: [$c.l_gt],  
                registry: [$c.l_G->l_registry],  
                #array (  
                     expr: $e.ci->func,  
                     size: $e.ci->func->tt != 0,  
                ): #( call: $e ),  
                #array (  
                     expr: $c.base[$i],  
                     size: $c.top - $c.base,  
                     base: 1  
                )  
           )  
      )  
 }  
 -->  
      <Type Name="lua_State">   
           <DisplayString>lua_State</DisplayString>  
           <Expand>  
                <Item Name="[stack size]">top-base</Item>  
                <IndexListItems>  
                     <Size>top-base</Size>  
                     <ValueNode>base[$i]</ValueNode>  
                </IndexListItems>  
                <Item Name="[globals]">l_gt</Item>  
                <Item Name="[registry]">l_G->l_registry</Item>                 
                <Item Name="[call]" Condition="ci->func->tt!=0">ci->func</Item>  
           </Expand>  
      </Type>   
 <!--       
 Node {  
      preview (  
           #( $c.i_key.tvk, " = ", $c.i_val )  
      )  
      children (  
           #(  
                key: $c.i_key.tvk,  
                val: $c.i_val,  
                #if( $c.i_key.nk.next != 0 ) (  
                     #( next: $c.i_key.nk.next )  
                )  
           )  
      )  
 }  
 -->  
      <Type Name="Node">   
           <DisplayString>{i_key} = {i_val}</DisplayString>  
           <Expand>  
                <Item Name="[val]">i_val</Item>  
           </Expand>  
      </Type>   
 <!--       
 lua_TValue {  
      children (  
           #switch($c.tt)  
           #case 2 (                                   ; LUA_TLIGHTUSERDATA  
                ptr: #((const char*)($c.value.p))  
           )  
           #case 5 (                                    ; LUA_TTABLE  
                #(  
                     [raw]: [$c,!],  
                     array size: $c.value.gc->h.sizearray,  
                     #array (  
                          expr: $e.value.gc->h.metatable,  
                          size: $e.value.gc->h.metatable != 0,  
                     ): #( metatable: $e ),  
                     #array (  
                          expr: $c.value.gc->h.array[$i],  
                          size: $c.value.gc->h.sizearray,  
                          base: 1  
                     ),  
                     #array (  
                          expr: #( $c.value.gc->h.node[$i], 2 ),  
                          size: (1<<$c.value.gc->h.lsizenode),  
                          base: 1  
                     ): #( hash part: $e )  
                )       
           )  
           #case 6 (                                   ; LUA_TFUNCTION  
                #if ($c.value.gc->cl.c.isC) (  
                     #(  
                          env: $c.value.gc->cl.c.env,  
                          #array (  
                               expr: $e.value.gc->cl.c.upvalue[$i],  
                               size: $e.value.gc->cl.c.nupvalues,  
                          ): #( upvalues: $e )  
                     )  
                ) #else (  
                     #($c.value.gc->cl.l)  
                )  
           )  
           #case 7 (                                   ; LUA_TUSERDATA  
                #(  
                     #array (  
                          expr: $e.value.gc->u.uv.metatable,  
                          size: $e.value.gc->u.uv.metatable != 0,  
                     ): #( metatable: $e ),  
                     env: $c.value.gc->u.uv.env,  
                     ptr: #((const char*)((&$c.value.gc->u)+1)),  
                     size: $c.value.gc->u.uv.len  
                )  
           )  
           #case 8 ( #($c.value.gc->th) )          ; LUA_TTHREAD  
      )  
      preview (  
           #switch($c.tt)  
           #case 0 ( "nil" )                         ; LUA_TNIL  
           #case 1 (  
                #if ($c.value.b == 0) (  
                     "false"  
                ) #else (  
                     "true"  
                )  
           )  
           #case 2 (                                   ; LUA_TLIGHTUSERDATA  
            #($c.value.p, " lightuserdata") )  
           #case 3 (                                   ; LUA_TNUMBER  
            #("number=", $c.value.n) )  
           #case 4 (                                   ; LUA_TSTRING  
            #( $c.value.gc->ts) )  
           #case 5 (      ; LUA_TTABLE  
                #( "table" )  
           )  
           #case 6 ( #($c.value.gc->cl) )          ; LUA_TFUNCTION  
           #case 7 ( #($c.value.gc->u) )          ; LUA_TUSERDATA  
           #case 8 ( #($c.value.gc->th) )          ; LUA_TTHREAD  
           #default ( "empty" )  
           )  
      )  
 }  
 -->  
      <Type Name="lua_TValue">   
           <DisplayString Condition="tt==0">nil</DisplayString>  
           <DisplayString Condition="tt==1">{(bool)value.b}</DisplayString>  
           <DisplayString Condition="tt==2">{value.p} lightuserdata</DisplayString>  
           <DisplayString Condition="tt==3">number={value.n}</DisplayString>  
           <DisplayString Condition="tt==4">{value.gc->ts}</DisplayString>  
           <DisplayString Condition="tt==5">{value.gc->h}</DisplayString>  
           <DisplayString Condition="tt==6">function {value.gc->cl}</DisplayString>  
           <DisplayString Condition="tt==7">userdata {value.gc->u}</DisplayString>  
           <DisplayString Condition="tt==8">thread {value.gc->th}</DisplayString>  
           <DisplayString>empty</DisplayString>  
           <Expand>  
                <Item Name="[lightuserdata]" Condition="tt==2">(const char *)value.p</Item>  
                <Item Name="[table]" Condition="tt==5">value.gc->h</Item>  
                <Item Name="[function]" Condition="tt==6">value.gc->cl</Item>  
                <Item Name="[userdata]" Condition="tt==7">value.gc->u</Item>  
                <Item Name="[thread]" Condition="tt==8">value.gc->th</Item>  
           </Expand>  
      </Type>   
 <!--       
 Udata {  
      preview (  
           #( "userdata size=", $c.uv.len, " ptr=", #((void*)((&$c)+1)) )  
      )  
 }  
 -->  
      <Type Name="Udata">   
           <DisplayString>userdata</DisplayString>  
      </Type>   
 <!--       
 CClosure {  
      preview (  
           $c.f  
      )  
 }  
 -->  
      <Type Name="CClosure">   
           <DisplayString>{f}</DisplayString>  
      </Type>   
 <!--       
 LClosure {  
      preview (  
           "Lua function"  
      )  
 }  
 -->  
      <Type Name="LClosure">   
           <DisplayString>Lua function</DisplayString>  
      </Type>   
 <!--       
 Closure {  
      preview (  
           #if ($c.c.isC) ( #($c.c) )  
           #else ( #($c.value.gc->cl.l) )  
      )  
 }  
 -->  
      <Type Name="Closure">   
           <DisplayString Condition="c.isC">{c}</DisplayString>  
           <DisplayString Condition="!c.isC">{value.gc->cl.l}</DisplayString>  
      </Type>   
 <!--       
 Table {  
      children (  
                #(  
                     [raw]: [$c,!],  
                     [array size]: $c.sizearray,  
                     #array (  
                          expr: $e.metatable,  
                          size: $e.metatable != 0,  
                     ): #( metatable: $e ),  
                     #array (  
                          expr: $c.array[$i],  
                          size: $c.sizearray,  
                          base: 1  
                     ),  
                     #array (  
                          expr: #( $c.node[$i], 2 ),  
                          size: (1<<$c.lsizenode),  
                          base: 1  
                     ): #( key: $e )  
                )       
      )  
      preview (  
           #( "table" )  
      )  
 }  
 -->  
      <Type Name="Table">   
           <DisplayString>table</DisplayString>  
           <Expand>  
                <Item Name="[array size]">sizearray</Item>  
                <Item Name="metatable" Condition="metatable!=0">metatable</Item>  
                <IndexListItems>  
                     <Size>sizearray</Size>  
                     <ValueNode>array[$i]</ValueNode>  
                </IndexListItems>  
                <IndexListItems>  
                     <Size>1&lt;&lt;lsizenode</Size>  
                     <ValueNode>node[$i]</ValueNode>  
                </IndexListItems>  
           </Expand>  
      </Type>   
 <!--       
 TString {  
      preview (  
           #( (const char *) (&($c.tsv)+1) )  
      )  
 }  
 -->  
      <Type Name="TString">   
           <DisplayString>{(const char *)(&amp;tsv+sizeof(*this)/sizeof(tsv)),s}</DisplayString>  
      </Type>   
      <Type Name="TKey">  
           <DisplayString>{tvk}</DisplayString>  
      </Type>  
 </AutoVisualizer>  



Lua 5.2:

 <?xml version="1.0" encoding="utf-8"?>   
 <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">   
      <Type Name="lua_State">   
           <DisplayString>[{top-(ci->func+1)}]</DisplayString>  
           <Expand>  
                <IndexListItems>  
                     <Size>top-(ci->func+1)</Size>  
                     <ValueNode>ci->func[$i+1]</ValueNode>  
                </IndexListItems>  
                <Item Name="[registry]">l_G->l_registry</Item>                 
           </Expand>  
      </Type>   
      <Type Name="Node">   
           <DisplayString>{i_key} = {i_val}</DisplayString>  
           <Expand>  
                <Item Name="[val]">i_val</Item>  
           </Expand>  
      </Type>   
      <Type Name="lua_TValue">   
           <DisplayString Condition="this==&amp;luaO_nilobject_">nil</DisplayString>  
           <!-- Lua non-number values encoded in NaN values - if not NaN, is a number -->  
           <DisplayString Condition="((*(((int *)this)+1))&amp;0x7fffff00)!=0x7ff7a500">number {*(lua_Number *)this}</DisplayString>  
           <DisplayString Condition="((*(((int *)this)+1))&amp;0xf)==0">nil</DisplayString>  
           <DisplayString Condition="((*(((int *)this)+1))&amp;0xf)==1">boolean {(bool)((Value *)this)->b}</DisplayString>  
           <DisplayString Condition="((*(((int *)this)+1))&amp;0xf)==2">lightuserdata {(void *)((Value *)this)->p}</DisplayString>  
           <!--DisplayString Condition="((*(((int *)this)+1))&amp;0xf)==3">number {*(lua_Number *)this}</DisplayString-->  
           <DisplayString Condition="((*(((int *)this)+1))&amp;0xf)==4">string {((Value *)this)->gc->ts}</DisplayString>  
           <DisplayString Condition="((*(((int *)this)+1))&amp;0xf)==5">table {(void *)((Value*)this)->gc}</DisplayString>  
           <DisplayString Condition="((*(((int *)this)+1))&amp;0xf)==6">function {(void *)((Value*)this)->gc}</DisplayString>  
           <DisplayString Condition="((*(((int *)this)+1))&amp;0xf)==7">userdata {(void *)((Value*)this)->gc}</DisplayString>  
           <DisplayString Condition="((*(((int *)this)+1))&amp;0xf)==8">thread {(void *)((Value*)this)->gc}</DisplayString>  
           <DisplayString>unknown</DisplayString>  
           <Expand>  
                <Item Name="[lightuserdata]" Condition="((*(((int *)this)+1))&amp;0xf)==2">(void *)((Value *)this)->p</Item>  
                <Item Name="[table]" Condition="((*(((int *)this)+1))&amp;0xf)==5">((Value*)this)->gc->h</Item>  
                <Item Name="[function]" Condition="((*(((int *)this)+1))&amp;0xf)==6">((Value*)this)->gc->cl</Item>  
                <Item Name="[userdata]" Condition="((*(((int *)this)+1))&amp;0xf)==7">((Value*)this)->gc->u</Item>  
                <Item Name="[thread]" Condition="((*(((int *)this)+1))&amp;0xf)==8">((Value*)this)->gc->th</Item>  
           </Expand>  
      </Type>   
      <Type Name="Udata">   
           <DisplayString>userdata</DisplayString>  
      </Type>   
      <Type Name="CClosure">   
           <DisplayString>{f}</DisplayString>  
      </Type>   
      <Type Name="LClosure">   
           <DisplayString>Lua function</DisplayString>  
      </Type>   
      <Type Name="Closure">   
           <DisplayString>{c}</DisplayString>  
      </Type>   
      <Type Name="Table">   
           <DisplayString>table</DisplayString>  
           <Expand>  
                <Item Name="[array size]">sizearray</Item>  
                <Item Name="metatable" Condition="metatable!=0">metatable</Item>  
                <IndexListItems>  
                     <Size>sizearray</Size>  
                     <ValueNode>array[$i]</ValueNode>  
                </IndexListItems>  
                <IndexListItems>  
                     <Size>1&lt;&lt;lsizenode</Size>  
                     <ValueNode>node[$i]</ValueNode>  
                </IndexListItems>  
           </Expand>  
      </Type>   
      <Type Name="TString">   
           <DisplayString>{(char *)(&amp;tsv+sizeof(*this)/sizeof(tsv)),s}</DisplayString>  
      </Type>   
      <Type Name="TKey">  
           <DisplayString>{tvk}</DisplayString>  
      </Type>  
 </AutoVisualizer>  

3 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Thanks for the help, I am programming in cocos2dx and just was annoyed I couldn't look at the lua stack or debug any tables. I modified your code slightly so it would only show the array part if there was one and only shows table values that don't have nil keys. Technically you can have nil values in tables if its set weak. Works in 2015 atleast


    <Type Name="Table">
    <DisplayString>table</DisplayString>
    <Expand>
    <Item Name="metatable" Condition="metatable!=0">metatable</Item>
    <Item Name="[array size]">sizearray</Item>
    <IndexListItems Condition="sizearray != 0">
    <Size>sizearray</Size>
    <ValueNode>array[$i]</ValueNode>
    </IndexListItems>
    <CustomListItems MaxItemsPerView="5000" ExcludeView="Test">
    <Variable Name="ibucket" InitialValue="0" />
    <Variable Name="pbucket" InitialValue="(Node*)0" />
    <Loop>
    <If Condition="pbucket == 0">
    <Break Condition="ibucket >= lsizenode"/>
    <Exec>pbucket = &amp;node[ibucket++]</Exec>
    </If>
    <!-- Skips nill keys -->
    <If Condition="pbucket->i_key.tvk.tt !=0">
    <Item>pbucket</Item>
    </If>
    <Exec>pbucket = pbucket->i_key.nk.next</Exec>
    </Loop>
    </CustomListItems>
    </Expand>
    </Type>

    ReplyDelete
  3. Hey, I'm glad it was of some help to you! Thanks for the update!

    ReplyDelete