Tracing stdout and stderr in Tcl

放肆的年华 提交于 2019-12-06 20:54:29

The problem with your attempted solution is that stdout, stderr and stdin are not variables but names of the so-called "channels". In essence, they're in a separated namespace (not a namespace operated by the namespace Tcl command!): you can get a list of them using the chan names command but you can't, say, rename a channel, or assign a value to it or unset it: these operations simply have no sense on channels and will affect a variable by that name instead.

An approach to trace a channel is to actually subvert it with another—"script-level"—channel and "proxy" all operations. This trick uses a lesser-known Tcl's feature: when you close one of Tcl's standard channels and immediately open a channel (no matter if "real" or "script-level"), that will gets registered in place of that standard channel just closed. Hence if we close a standard channel and immediately create our own "proxy" channel in its place we hence subvert that standard channel.

Those "script-level" ("reflected") channels require Tcl ≥ 8.5.

Here's a sketch of subverting stdout which requires Linux (for /proc/self/fd/<fileno> support).

proc traceChan {cmd chan args} {
    global stdout

    puts stderr "Trace on $chan; cmd=$cmd; args=$args"

    switch -- $cmd {
        initialize {
            return [list initialize finalize watch write configure cget cgetall]
        }
        finalize {
            chan close $stdout
        }
        watch {
            # FIXME: not implemented
        }
        write {
            set data [lindex $args 0]
            chan puts -nonewline $stdout $data
            return [string length $data]
        }
        configure {
            return [chan config $stdout {*}$args]
        }
        cget {
            return [chan cget $stdout {*}$args
        }
        cgetall {
            return [chan configure $stdout]
        }
    }
}

set fn [file readlink /proc/self/fd/1]
set conf [chan config stdout]
chan close stdout
chan create write ::traceChan
set stdout [open $fn w]
chan configure stdout {*}$conf

puts [chan names]
puts test
chan flush stdout
chan close stdout

On my system it botches terminal settings (stdout is connected to a terminal), and requires me to execute reset followed by stty sane after the script exits but at least it does the job done.

Actual problems with this script:

  • In fact we lie about the number of bytes written: we have no idea how many bytes the underlying stdout channel will write as it depends on a number of things.
  • We don't handle channel events at all.

These problems can be addressed by writing a C module implementing such a proxying: with the C API, you would have access to the underlying file descriptor/handle (so no requirement for /proc/self/fd/...) and a Tcl object wrapping it (so you could possibly just clone it right away) and know how many bytes were written by the underlying channel.


Oh, and note that if you're fine with throwing the data sent by the script to the subverted standard channels away, just do no dances with re-opening the real underlying file, closing it, writing data to it etc. The solution will then boild down to performing chan create immediately after chan close and tracing writes in the tracing procedure. In response to the write call your tracing routine will still need to return a number of bytes thrown away.


Please also read the chan and refchan manual pages.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!