Is there any way to count the commands before execution (may be callstack number including tclcmds) from specified proc name? I think it is needed to assume that the source is available (not for precomiled).
Thanking you.
Dynamic Analysis
You can use traces to find how many commands are executed during the execution of a particular procedure. Under the assumption that the command is not re-entered (i.e., isn't recursive) you do:
proc theProcedureOfInterest {} {
# Whatever in here...
for {set i 0} {$i < 5} {incr i} {
puts i=$i
}
}
trace add execution theProcedureOfInterest {enter leave enterstep} countCalls
proc countCalls {cmd args} {
global counter
switch [lindex $args end] {
enter {
set counter 0
}
enterstep {
incr counter
puts >>>$cmd
}
leave {
puts "$counter calls in $cmd"
}
}
}
theProcedureOfInterest
If you execute the above code, you get this output:
>>>for {set i 0} {$i < 5} {incr i} {
puts i=$i
}
>>>set i 0
>>>puts i=0
i=0
>>>incr i
>>>puts i=1
i=1
>>>incr i
>>>puts i=2
i=2
>>>incr i
>>>puts i=3
i=3
>>>incr i
>>>puts i=4
i=4
>>>incr i
12 calls in theProcedureOfInterest
That code has 12 command calls inside it, and you can count them yourself too.
This will also trace into procedures called from that procedure (and making it handle recursive calls is possible, but rather more involved). Be aware that changing the definition of the procedure will remove the trace (just reapply it if desired) and also note that this sort of tracing has a substantial performance impact (it greatly inhibits possible optimizations in Tcl's bytecode compiler).
Static Analysis
To get a static analysis of the code, you need the dkf-improved-disassembler branch (I've not merged it yet). Then, you can do:
set disassembled [::tcl::unsupported::getbytecode script {
# Whatever in here...
for {set i 0} {$i < 5} {incr i} {
puts i=$i
}
}]
set commandCount [llength [dict get $disassembled commands]]
You can also look at the commands element to see the identified commands (dict get [lindex [dict get $disassembled commands] $n] source). It will inspect into commands like for, but not into custom commands with bodies (since it doesn't understand that they're part of the code as opposed to just a funny string). It also has no idea how often they're executed; it's static analysis after all.
Related
I am trying to implement a single stepping using GDB command file. I would like to stop executing the command file whenever I encounter any breakpoint.
Is there a way to detect that a breakpoint was hit? and also how can I stop executing the command file after that.
I checked the documantation and it seems like I only can have break commands for certain breakpoints. But not for a random breakpoint.
I have something like this:
printf "single stepping\n"
set $steps_count = 0
while ($steps_count < 5)
set $steps_count = $steps_count + 1
printf "program counter 0x%x\n", $pc
printf "next #%d\n", $steps_count
next
end
And this is what I want to achieve:
printf "single stepping\n"
set $steps_count = 0
while (# breakpoint is not hit)
set $steps_count = $steps_count + 1
printf "program counter 0x%x\n", $pc
printf "next #%d\n", $steps_count
next
end
# end the execution of the command file.
update:
I tried to use the follwing:
# set the end breakpoint
break *0x199e
commands
stop 1
quit
end
break *0x1980
# set a certain once we encounter the first breakpoint
commands
set $count = 0
while $count < 5
printf "#PC: 0x%x", $pc
set $count = $count + 1
stepi
end
end
This should start logging the program counter once the first breakpoint is hit. But I don't know why this break command doesn't work. Once the breakpoint is hit the program stops and the while loop is not entered!
Update:
according to the gdb documentation:
Any other commands in the command list, after a command that resumes execution, are ignored. This is because any time you resume execution (even with a simple next or step), you may encounter another breakpoint--which could have its own command list, leading to ambiguities about which list to execute.
so I don't think it is a good idea to put a loop that has step or next in break commands.
Try something like:
commands 1-10 # for breakpoints number 1 through 10
set $breakpoint_hit = 1
end
Then check for that in your loop.
I am having trouble with an expect script not evaluating arguments. Everything in the puts ${file_id} block (simplified obviously) gets placed onto a virtual machine and is later used to for configuration. The block you see puts the same code into a local directory for me to see if things are working properly.
global env
set env(os1) [lindex $argv 0]
set env(scratch_repo) /tmp/yum.repo_[pid]
set file_id [ open ${env(scratch_repo)} "w"]
puts ${file_id} {
root_image=node-${env(os1)}
if {[string first r ${env(os1)}] == 0} {
create_node_byid 1 [string range ${env(os1)} 0 4]-64
} else {
create_node_byid 1 [string range ${env(os1)} 0 5]-64
}
}
Unfortunately, the log file looks exactly as above. The arguments are not being substituted properly and I can't figure out why. I've tried using regular variables, different syntax for referencing local and global variables but have had no luck getting this to work. Any thoughts?
As Etan Reisner pointed use double quotes in the puts command instead of braces, so that it will get replaced.
puts ${file_id} "
root_image=node-${env(os1)}
if {[string first r ${env(os1)}] == 0} {
create_node_byid 1 [string range ${env(os1)} 0 4]-64
} else {
create_node_byid 1 [string range ${env(os1)} 0 5]-64
}
"
Assuming env(os1) as Ubuntu, will produce the following content in the file
root_image=node-Ubuntu
if {-1 == 0} {
create_node_byid 1 Ubunt-64
} else {
create_node_byid 1 Ubuntu-64
}
Note : This will only do variable substitutions not evaluation of code as such. i.e. if-else commands won't be evaluated.
My script works fine (retrieves sftp prompt) when using one proc. But when I try to use proc inside proc, script gets stuck, and I do not know why.
Please do not refactor the code, that is not the point, I need to understand what is the issue here.
Working code:
proc sftp_connect {} {
set times 0;
set connection_retry 2
set timeout 1;
while { $times < $connection_retry } {
spawn sftp ${SFTP_USER}#${SFTP_SERVER}
expect {
timeout { puts "Connection timeout"; exit 1}
default {exit 2}
"*assword:*" {
send "${SFTP_PASSWORD}\n";
expect {
"sftp>" { puts "Connected"; set times [ expr $times+1]; exp_continue}
}
}
}
}
send "quit\r";
}
sftp_connect
Debug output:
expect: does "\r\nsftp> " (spawn_id exp5) match glob pattern "sftp>"? yes
But after moving send password into separate proc, expect does not retrieve sftp prompt anymore ("sftp>"):
proc sftp_send_password {} {
send "${SFTP_PASSWORD}\n";
expect {
"sftp>" { puts "Connected"; set times [ expr $times+1]; exp_continue}
}
}
proc sftp_connect {} {
set times 0;
set connection_retry 2
set timeout 1;
while { $times < $connection_retry } {
spawn sftp ${SFTP_USER}#${SFTP_SERVER}
expect {
timeout { puts "Connection timeout"; exit 1}
default {exit 2}
"*assword:*" { sftp_send_password }
}
}
send "quit\r";
}
sftp_connect
Debug output:
expect: does "" (spawn_id exp0) match glob pattern "sftp>"? yes
I don't have my copy of "Exploring Expect" handy, but I think you're running into a variable scoping issue. spawn invisibly sets a variable named spawn_id. When you call spawn in a proc, that variable is scoped only for that proc. Declare it as global:
proc sftp_connect {} {
global spawn_id
# ... rest is the same
}
I think you don't have to do the same thing in sftp_send_password because expect has a more forgiving scoping scheme than Tcl (if expect does not find a local variable, look in the global namespace).
Your sftp_send_password proc will not affect the times variable in sftp_connect though, due to the same variable scoping issue. I'd recommend
proc sftp_send_password {times_var} {
upvar 1 $times_var times ;# link this var to that in the caller
send "${SFTP_PASSWORD}\n";
expect {
"sftp>" { puts "Connected"; incr times; exp_continue}
}
# note use of `incr` instead of `expr`
}
And then the sftp_connect proc sends the times variable name:
sftp_send_password times
The following is from the expect's man page:
Expect takes a rather liberal view of scoping. In particular, variables read by commands specific to the Expect program will be sought first from
the local scope, and if not found, in the global scope. For example, this obviates the need to place global timeout in every procedure you write
that uses expect. On the other hand, variables written are always in the local scope (unless a global command has been issued). The most common
problem this causes is when spawn is executed in a procedure. Outside the procedure, spawn_id no longer exists, so the spawned process is no longer
accessible simply because of scoping. Add a global spawn_id to such a procedure.
I am working in VCS UCLI (ie, the command line interface) and am having trouble getting VCS to display various state variables, of a typedef'd enum type, value as the name rather than the number. For example, I have some SystemVerilog like this:
typedef enum logic [1:0] {A, B, C} state_t;
state_t s;
...
Now in ucli, I want to see the value of s (say its in state A) so I type something like:
ucli% get s
0
ucli% get s -radix symbolic
0
ucli% show s -value
s 0
ucli% show s -value -radix symbolic
s 0
ucli% show s -value -type
s 0 { ENUM state_t { {A 0} {B 1} {C 2} } }
(Or something like that). I have read the ucli user guide, and it seems like symbolic radix, the only one I know of that might possibly be close, just uses the raw value from the enum, not the enum name. I have tried calling the .name() method for variable s using the call command in ucli (ucli% call {$display("%s", s.name())}), but it doesnt seem to be supported. I know VCS has the capacity to print the enum name, it certainly can in DVE, but I am having trouble coming up with ways to get to show me in the ucli.
Does anyone know how to get the ucli to print the enum name instead of the number when queried? Is enum-type radix somehow (user-defined like in DVE?), use some SystemVerilog system call to get the name, anything like that?
(Note, I understand I could just use the DVE, but I am trying to use the ucli to simply the interface for potential users, this is for educational purposes and I want to mask alot of the ucli interface (and VCS interface in general) to not overwhelm students and get some variables easily; Im turning the vcs ucli into a simple processor simulator)
++++++++++++ UPDATE ++++++++++++
I came up with a very hacky solution but I would really like a better approach. I quickly wrote my own wrapper for show (called eshow) whish ill replace any -value with the enum name if -radix enum is set:
#
# An extension of show to include "-radix enum"
#
# Borrowed from http://wiki.tcl.tk/17342
# Credit to Richard Suchenwirth (12-8-2006)
proc getopt {_argv name {_var ""} {default ""}} {
upvar 1 $_argv argv $_var var
set pos [lsearch -regexp $argv ^$name]
if {$pos>=0} {
set to $pos
if {$_var ne ""} {
set var [lindex $argv [incr to]]
}
set argv [lreplace $argv $pos $to]
return 1
} else {
if {[llength [info level 0]] == 5} {set var $default}
return 0
}
}
proc eshow {args} {
set argv $args
# If radix is not specified or value is not specified, then dont bother doing anything but regular show
if { 0 == [getopt argv -radix radix] } {
return [eval show $args]
}
if { 0 == [getopt argv -value] } {
return [eval show $args]
}
# If radix isnt enum, just pass off to regular show
if { 0 == [string equal -nocase $radix "enum"] } {
return [eval show $args]
}
# Now get the signal, its value and its type
set var [lindex [eval show $argv] 0]
set val [lindex [show $var -value] 1]
set typ [lindex [show $var -type] 1]
# If the type isnt an enum, error
if { 0 == [string equal -nocase [lindex $typ 0] "ENUM"] } {
return "The type of variable $var is not an enumerated type"
}
# Process the enumerations
set enuml [lindex $typ 2]
# Find the value name
foreach v $enuml {
if { $val == [lindex $v 1] } {
set enumname [lindex $v 0]
break
}
}
# If could not be found....
if { 0 == [info exists enumname] } {
return "The variabel $var has a value which does not map"
}
# Get rid of radix from args
getopt args -radix trashcan
# Replace all values with the name
set retval [eval show $args]
set retshow $retval
foreach v [lsearch -all $retval $val] {
set retshow [lreplace $retshow $v $v $enumname]
}
return $retshow
}
Thus, if I type any other non-radix enum eshow commands, it will pass to show, but otherwise, it will replace all values with thier names and return the same thing show would with the replacement. As I said, I REALLY want a better solution, but in case anyone wants to use my function, here it is.
The script passes two parameter values to another instance of the script. So the built-in parameter variable, 0, contains the number of passed parameters. 1 is in the below example "C:/Windows" and 2 is "/switchtest"
It is possible to assign the parameter values to strParam1 and strParam2 with the traditional method outside the function (with the single equal sign). However, inside a function, the assignments fail.
If they are assigned in a loop with the := sign, it seems to work.
Why is it? Can anybody explain this behavior?
strParam1 = %1%
strParam2 = %2%
msgbox, 64, Outside the Function, number of parameters:%0%`npath: %strParam1%`nswitch: %strParam2%
test_params()
strPath := "C:/Windows"
strSwitch := "/switchtest"
RunWait "%A_AhkPath%" "%A_ScriptFullPath%" "%strPath%" "%strSwitch%"
test_params() {
global 0
; this works
; loop %0%
; strParam%A_Index% := %A_Index%
; this causes an error: "This dynamic variable is blank. If this variable was not intended to be dynamic, remove the % symbols from it."
; strParam1 := %1%
; strParam2 := %2%
; this passes empty values; however, this method works outside the function.
strParam1 = %1%
strParam2 = %2%
msgbox, 64, Inside the Function, number of parameters:%0%`npath: %strParam1%`nswitch: %strParam2%
if strParam2
exitapp
}
You had the right idea with global 0; that allows %0% to carry into the function from toplevel. You just need to declare global 1, 2 as well.
Even if you do this, you can't use := to assign them to variables, because := deals with expressions and there is no syntax to use them in expressions (normally a variable is referred to in an expression with the variable name alone, without %%; obviously 1 and 2 are interpreted as actual numbers instead of variables).
#echristopherson answered the question, but I'd like to propose a workaround. This assumes you're using AutoHotkey_L.
If you run the test script with the args "a b c", it gives you this.
3
1, a
2, b
3, c
The test:
argv := args()
test := argv.MaxIndex() "`n"
for index,param in argv
test .= index ", " param "`n"
MsgBox % test
And the function:
args() {
global
local _tmp, _out
_out := []
Loop %0% {
_tmp := %A_Index%
if _tmp
_out.Insert(_tmp)
}
return _out
}