This first edition was written for Lua 5.0. While still largely relevant for later versions, there are some differences.
The fourth edition targets Lua 5.3 and is available at Amazon and other bookstores.
By buying the book, you also help to support the Lua project.

23.1.2 – Accessing Upvalues

The debug library also allows us to access the upvalues that a Lua function uses, with getupvalue. Unlike local variables, however, a function has its upvalues even when it is not active (this is what closures are about, after all). Therefore, the first argument for getupvalue is not a stack level, but a function (a closure, more precisely). The second argument is the upvalue index. Lua numbers upvalues in the order they are first referred in a function, but this order is not relevant, because a function cannot have two upvalues with the same name.

You can also update upvalues, with debug.setupvalue. As you might expect, it has three parameters: a closure, an upvalue index, and the new value. Like setlocal, it returns the name of the upvalue, or nil if the upvalue index is out of range.

The following code shows how we can access the value of any given variable of a calling function, given the variable name:

    function getvarvalue (name)
      local value, found
      -- try local variables
      local i = 1
      while true do
        local n, v = debug.getlocal(2, i)
        if not n then break end
        if n == name then
          value = v
          found = true
        i = i + 1
      if found then return value end
      -- try upvalues
      local func = debug.getinfo(2).func
      i = 1
      while true do
        local n, v = debug.getupvalue(func, i)
        if not n then break end
        if n == name then return v end
        i = i + 1
      -- not found; get global
      return getfenv(func)[name]
First, we try a local variable. If there is more than one variable with the given name, we must get the one with the highest index; so we must always go through the whole loop. If we cannot find any local variable with that name, then we try upvalues. First, we get the calling function, with debug.getinfo(2).func, and then we traverse its upvalues. Finally, if we cannot find an upvalue with that name, then we get a global variable. Notice the use of the argument 2 in the calls to debug.getlocal and debug.getinfo to access the calling function.