public inbox for systemtap@sourceware.org
 help / color / mirror / Atom feed
* python tapset
@ 2011-11-17 21:53 Stan Cox
  2011-11-18  0:42 ` Josh Stone
  0 siblings, 1 reply; 3+ messages in thread
From: Stan Cox @ 2011-11-17 21:53 UTC (permalink / raw)
  To: systemtap

[-- Attachment #1: Type: text/plain, Size: 1500 bytes --]

This is a first cut at a tapset that enables probing python applications.
  python_backtrace - get backtraces for the running python application
  python_get_variable [VARIABLE] - get value of a running python
         application variable.  VARIABLE can be a variable wildcard,
         aotherwise defaults to *

# python-backtrace example
stap -I tapset -g -c '/usr/local/bin/python ./celsius.py 30' celsius-bt.stp
#0 celsius_to_farenheit (celsius:int ) at ./celsius.py:7
#1 main  at ./celsius.py:19
#2 <module>  at ./celsius.py:3
...

# python-get_variable examples
stap -I tapset -g -c '/usr/local/bin/python ./celsius.py 30' celsius-var.stp
  global str __builtins__ in <module> at ./celsius.py
  global str __name__ in <module> at ./celsius.py = "__main__"
  global str __file__ in <module> at ./celsius.py = "./celsius.py"
int celsius in main at ./celsius.py = 30
tuple atuple in celsius_to_farenheit at ./celsius.py =  "a", "b", "c",
list alist in celsius_to_farenheit at ./celsius.py = [ 1, 2, 3,]
set aset in celsius_to_farenheit at ./celsius.py = { 1, 2, 3,}
dict adict in celsius_to_farenheit at ./celsius.py = { 1:"a" 2:"b" 3:"c" }
int nine in celsius_to_farenheit at ./celsius.py = 9
int five in celsius_to_farenheit at ./celsius.py = 5
int thirty_two in celsius_to_farenheit at ./celsius.py = 32
int i in celsius_to_farenheit at ./celsius.py = 1

stap -I tapset -g -c '/usr/local/bin/python ./celsius.py 30' 
celsius-var.stp 'ni*'
int nine in celsius_to_farenheit at ./celsius.py = 9


[-- Attachment #2: celsius.py --]
[-- Type: text/plain, Size: 603 bytes --]

#!/usr/bin/python

import sys

aglobal9 = 9

def celsius_to_farenheit(celsius):
    atuple = "a", "b", "c"
    alist = [1, 2, 3]
    aset = {1, 2, 3}
    adict = { 1 : "a", 2 : "b", 3 : "c" }

    nine = aglobal9
    five = 5
    thirty_two = 32
    i = 1
    return str((nine * celsius) / five + thirty_two)

def main():
    if (len(sys.argv) < 2):
        print "Usage: " + sys.argv[0] + " Temp"
        return 1

    celsius = int(sys.argv[1])
    print str(celsius) + " Celsius " + " is " + celsius_to_farenheit(celsius) + " Farenheit"
    return 0

if __name__ == "__main__":
    sys.exit(main())


[-- Attachment #3: python.diff --]
[-- Type: text/plain, Size: 534 bytes --]

--- ../../python/src/Python/ceval.c.orig	2011-11-17 15:54:06.287304375 -0500
+++ ../../python/src/Python/ceval.c	2011-11-17 15:57:42.875596649 -0500
@@ -19,6 +19,8 @@
 
 #include <ctype.h>
 
+#include <sys/sdt.h>
+
 #ifndef WITH_TSC
 
 #define READ_TIMESTAMP(var)
@@ -1094,6 +1096,8 @@ PyEval_EvalFrameEx(PyFrameObject *f, int
         }
 #endif
 
+	DTRACE_PROBE1 (python, backtrace, PyEval_GetFrame());
+	DTRACE_PROBE1 (python, get_variable, PyEval_GetFrame());
 
         /* Main switch on opcode */
         READ_TIMESTAMP(inst0);

[-- Attachment #4: python.stp --]
[-- Type: text/plain, Size: 10886 bytes --]

# Global Python variables

global gp_vars_seen			# which vars we have seen
global gp_dict_key = 0			# set by get_dict_item
global gp_dict_value = 0
global gp_dict_hash = 0
global gp_probe_backtrace_ = 0		# controls which probes fire
global gp_probe_get_variable_ = 0

# FUNCTION GET_TYPE
# OBJ: PyObject
# RETURNS: string representation of type

function get_type:string (obj) {
    if (obj == 0)
	return ""
    ob_type = @cast (obj, "PyObject", "/usr/local/bin/python")->ob_type
    if (ob_type == 0)
	return ""
    ob_type_name = @cast (ob_type, "PyTypeObject", "/usr/local/bin/python")->tp_name
    return set_string (ob_type_name)
}

# FUNCTION SET_STRING
# STR_P: pointer to string
# RETURNS: string as a stap string

function set_string (str_p) {
    if (str_p == 0)
	return ""
    else
	return user_string (str_p)
}

# FUNCTION GET_SEQUENCE_ITEM
# TUPLE: array of PyObject
# I: index to retrieve 
# SEQ_TYPE: PyTupleObject or PyListObject
# RETURNS: array entry PyObject

function get_sequence_item (tuple, i, seq_type) {
    if (seq_type == "tuple")
	ob_items = @cast (tuple, "PyTupleObject", "/usr/local/bin/python")->ob_item;
    else if (seq_type == "list")
	ob_items = @cast (tuple, "PyListObject", "/usr/local/bin/python")->ob_item;

    ob_item = user_long (ob_items + (i * %{ sizeof (intptr_t) %}))
    return ob_item
}

# FUNCTION DISPLAY_VALUE
# VALUE: PyObject
# PREFIX: prefix string to display
# SUFFIX: suffix string to display

function display_value (value, prefix, suffix) {
    if (value == 0) {
	return 0
    }
    value_type_str = get_type (value)
    if (value_type_str == "str") {
	value_s = @cast (value, "PyStringObject", "/usr/local/bin/python")->ob_sval;
	value_str = set_string (value_s)
	printf ("%s\"%s\"%s", prefix, value_str, suffix)
    }
    else if (value_type_str == "int") {
	arg_value_int = @cast (value, "PyIntObject", "/usr/local/bin/python")->ob_ival;
	printf ("%s%d%s", prefix, arg_value_int, suffix)
    }
    else if (value_type_str == "tuple" || value_type_str == "list") {
	n = @cast (value, "PyTupleObject", "/usr/local/bin/python")->ob_size;
	if (value_type_str == "list") printf (" = [")
	else printf (" = ")
	for (i = 0; i < n; i++) 
	    display_value (get_sequence_item (value, i, value_type_str), " ", ",")
	if (value_type_str == "list") printf ("]")
    }
    else if (value_type_str == "set") {
	printf (" = {")
	n = @cast (value, "PySetObject", "/usr/local/bin/python")->used;
	for (i = 0; i <= n; i++)
	    display_value (get_set_item (value, i), " ", ",")
	printf ("}")
    }
    else if (value_type_str == "dict") {
	printf (" = {")
	n = @cast (value, "PyDictObject", "/usr/local/bin/python")->ma_used;
	for (i = 0; i <= n; i++) {
	    get_dict_item (value, i)      
	    if (gp_dict_hash == 0 && gp_dict_key == 0)
		continue
	    display_value (gp_dict_key, " ", ":")
	    display_value (gp_dict_value, "", "")
	}
	printf (" }")
    }
}

# FUNCTION GET_SET_ITEM
# SET: PySetObject
# I: set index to retrieve
# RETURNS: set entry PyObject

function get_set_item (set, i) {
    entries = @cast (set, "PySetObject", "/usr/local/bin/python")->table;
    n = @cast (set, "PySetObject", "/usr/local/bin/python")->used;
    if (i > n)
	return 0
  
    return @cast (entries, "setentry", "/usr/local/bin/python")[i]->key
}

# FUNCTION GET_DICT_ITEM
# DICT: PySetObject
# I: set index to retrieve
# RETURNS: sets the global variables GP_DICT_KEY, GP_DICT_VALUE, GP_DICT_HASH

function get_dict_item (dict, i) {
    entries = @cast (dict, "PyDictObject", "/usr/local/bin/python")->ma_table;
    n = @cast (dict, "PyDictObject", "/usr/local/bin/python")->ma_used;
    if (i > n)
	return 0
  
    gp_dict_hash = @cast (entries, "PyDictEntry", "/usr/local/bin/python")[i]->me_hash
    gp_dict_key = @cast (entries, "PyDictEntry", "/usr/local/bin/python")[i]->me_key
    gp_dict_value = @cast (entries, "PyDictEntry", "/usr/local/bin/python")[i]->me_value;
}

# FUNCTION DISPLAY_DICT_VARIABLE
# DICT: local or global variable name dictionary
# VARIABLE: variable to retrieve
# NAMESPACE: local or global

function display_dict_variable (dict, variable, namespace, co_name_str, co_filename_str) {
    n = @cast (dict, "PyDictObject", "/usr/local/bin/python")->ma_used;
    for (i = 0; i < n; i++) {
	get_dict_item (dict, i)      
	key_str = set_string (@cast (gp_dict_key, "PyStringObject", "/usr/local/bin/python")->ob_sval)
	key_type_str = get_type (gp_dict_key)

	if (gp_dict_key == 0 || gp_dict_value == 0) break
	if (wildcard_match (key_str, variable)) {
	    foreach ([var, file] in gp_vars_seen) {
		if (var == key_str
		    && file == co_filename_str)
		    return 0
	    }
	    printf (" %s %s %s", namespace, key_type_str, key_str)
	    printf (" in %s at %s", co_name_str, co_filename_str)
	    display_value (gp_dict_value, " = ", " ")
	    printf ("\n")
	    gp_vars_seen[key_str, co_filename_str] = co_name_str
	}
    }
}

# FUNCTION WILDCARD_MATCH
# STR: String to check for a wildcard
# PATTERN: The wildcard pattern.  Wildcard special characters are limited to '*'

function wildcard_match (str, pattern) {
    if (pattern == "*")
	return 1

    # Does pattern start at the beginning of str or is it '*'?
    if (substr (pattern, 0, 1) != "*" && substr (str, 0, 1) != substr (pattern, 0, 1))
	return 0

    hunk = tokenize (pattern,"*")
    while (hunk != "")
    {
	# find pattern hunk in str
	if (isinstr (str, hunk) == 0) {
	    return 0
	}
	match = 0
	for (i = 0; i < strlen (str) - strlen (hunk) + 1; i++) {
	    if (hunk == substr (str, i, strlen (hunk))) {
		match = 1
		break
	    }
	}	
	if (!match) return 0
	    
	# skip past hunk in str
	str = substr (str, i, strlen (str)-i+1)
	hunk = tokenize ("","*")
    }

    # Does pattern end at the end of str or is it '*'?
    if (substr (pattern, strlen (pattern)-1, 1) != "*"
	&& substr (str, strlen (str)-1, 1) != substr (pattern, strlen (pattern)-1, 1))
	return 0
    else
	return 1
}

# PROBE BACKTRACE
# @1: Pathnames containing this string are ignored.  Default is "/lib/" to
#     ignore system python libraries

probe process ("/usr/local/bin/python").mark ("backtrace") if (gp_probe_backtrace_) {
    frame = $arg1
    %( $# == 1 %?
       skip_name = @1
       %:
       skip_name = "/lib/"
       %)
    frame_n = 0

    while (frame) {
	f_code = @cast (frame, "struct _frame", "/usr/local/bin/python")->f_code;
	co_filename = @cast (f_code, "PyCodeObject", "/usr/local/bin/python")->co_filename;
	co_filename_s = @cast (co_filename, "PyStringObject", "/usr/local/bin/python")->ob_sval;
	co_filename_str = set_string (co_filename_s)
	if (isinstr (co_filename_str, skip_name) == 1)
	    next
	co_name = @cast (f_code, "PyCodeObject", "/usr/local/bin/python")->co_name;
	co_name_s = @cast (co_name, "PyStringObject", "/usr/local/bin/python")->ob_sval;
	co_name_str = set_string (co_name_s)
	co_firstlineno = @cast (f_code, "PyCodeObject", "/usr/local/bin/python")->co_firstlineno;

	printf ("#%d %s ", frame_n, co_name_str)

	co_varnames = @cast (f_code, "PyCodeObject", "/usr/local/bin/python")->co_varnames;
	if (co_varnames == 0)
	    continue
	co_argcount = @cast (f_code, "PyCodeObject", "/usr/local/bin/python")->co_argcount;
	f_localsplus = @cast (frame, "struct _frame", "/usr/local/bin/python")->f_localsplus;
	for (i = 0; i < co_argcount; i++) {
	    if (i == 0) print ("(");
	    arg_name_str = user_string (@cast (get_sequence_item (co_varnames, i, "tuple"), "PyStringObject", "/usr/local/bin/python")->ob_sval)
	    arg_value = user_long (f_localsplus + (i * %{ sizeof (intptr_t) %}))
	    arg_type_name_str = get_type (arg_value)
	    printf ("%s:%s ", arg_name_str, arg_type_name_str)
	}

	if (co_argcount) printf (")");

	printf (" at %s:%d\n", co_filename_str, co_firstlineno)

	frame_n += 1
	frame = @cast (frame, "struct _frame", "/usr/local/bin/python")->f_back;
    }
}
    
# PROBE GET_VARIABLE
# @1: Variable wildcard to be matched.  Default is "*" to match all variables.
# @2: Pathnames containing this string are ignored.  Default is "/lib/" to
#     ignore system python libraries

probe process ("/usr/local/bin/python").mark ("get_variable") if (gp_probe_get_variable_) {
    frame = $arg1
    %( $# >= 1 %?
       variable = @1
       %:
       variable = "*"
       %)
    %( $# >= 2 %?
       skip_name = @2
       %:
       skip_name = "/lib/"
       %)
    frame_n = 0

    while (frame) {
	f_code = @cast (frame, "struct _frame", "/usr/local/bin/python")->f_code;
	co_filename = @cast (f_code, "PyCodeObject", "/usr/local/bin/python")->co_filename;
	co_filename_s = @cast (co_filename, "PyStringObject", "/usr/local/bin/python")->ob_sval;
	co_filename_str = set_string (co_filename_s)
	if (isinstr (co_filename_str, skip_name) == 1)
	    next
	co_name = @cast (f_code, "PyCodeObject", "/usr/local/bin/python")->co_name;
	co_name_s = @cast (co_name, "PyStringObject", "/usr/local/bin/python")->ob_sval;
	co_name_str = set_string (co_name_s)

	f_globals = @cast (frame, "struct _frame", "/usr/local/bin/python")->f_globals;
	if (f_globals != 0) {
	    display_dict_variable (f_globals, variable, "global", co_name_str, co_filename_str)
	}
	f_locals = @cast (frame, "struct _frame", "/usr/local/bin/python")->f_locals;
	if (f_locals != 0) {
	    display_dict_variable (f_locals, variable, "local", co_name_str, co_filename_str)
	}

	co_varnames = @cast (f_code, "PyCodeObject", "/usr/local/bin/python")->co_varnames;
	if (co_varnames == 0)
	    continue
	co_argcount = @cast (f_code, "PyCodeObject", "/usr/local/bin/python")->co_argcount;
	f_localsplus = @cast (frame, "struct _frame", "/usr/local/bin/python")->f_localsplus;
	ob_size = @cast (co_varnames, "PyTypeObject", "/usr/local/bin/python")->ob_size;
	for (i = 0; i < ob_size; i++) {
	    if (i < co_argcount - 1)
		continue
	    arg_name_str = user_string (@cast (get_sequence_item (co_varnames, i, "tuple"), "PyStringObject", "/usr/local/bin/python")->ob_sval)
	    if (! wildcard_match (arg_name_str, variable))
		continue
	    arg_value = user_long (f_localsplus + (i * %{ sizeof (intptr_t) %}))
	    arg_type_name_str = get_type (arg_value)
	    next_var = 0
	    foreach ([var, file] in gp_vars_seen)
	    if (var == arg_name_str
		&& file == co_filename_str)
		next_var = 1
	    if (next_var || arg_type_name_str == "") continue
	    gp_vars_seen[arg_name_str, co_filename_str] = co_name_str
	    printf ("%s %s", arg_type_name_str, arg_name_str)
	    printf (" in %s at %s", co_name_str, co_filename_str)
	    display_value (arg_value, " = ", " ")
	    printf ("\n")
	}

	frame_n += 1
	frame = @cast (frame, "struct _frame", "/usr/local/bin/python")->f_back;
    }
}

probe python_get_variable = process ("/usr/local/bin/python").mark ("get_variable")
{
    gp_probe_backtrace_ = 0
    gp_probe_get_variable_ = 1
}

probe python_backtrace = process ("/usr/local/bin/python").mark ("backtrace")
{
    gp_probe_backtrace_ = 1
    gp_probe_get_variable_ = 0
}

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: python tapset
  2011-11-17 21:53 python tapset Stan Cox
@ 2011-11-18  0:42 ` Josh Stone
  2011-11-22 20:26   ` Stan Cox
  0 siblings, 1 reply; 3+ messages in thread
From: Josh Stone @ 2011-11-18  0:42 UTC (permalink / raw)
  To: Stan Cox; +Cc: systemtap

Hi Stan,

On 11/17/2011 01:53 PM, Stan Cox wrote:
> This is a first cut at a tapset that enables probing python applications.
>   python_backtrace - get backtraces for the running python application
>   python_get_variable [VARIABLE] - get value of a running python
>          application variable.  VARIABLE can be a variable wildcard,
>          aotherwise defaults to *

Thanks for sharing!

Since this is heavily tied to cpython internals, this will be a tapset
that better belongs in their tree, much like python already ships a
tapset (at least in fedora) for the function entry/return probes.  We
can certainly hammer on it here first, though.

> --- ../../python/src/Python/ceval.c.orig	2011-11-17 15:54:06.287304375 -0500
> +++ ../../python/src/Python/ceval.c	2011-11-17 15:57:42.875596649 -0500
> @@ -19,6 +19,8 @@
>  
>  #include <ctype.h>
>  
> +#include <sys/sdt.h>
> +
>  #ifndef WITH_TSC
>  
>  #define READ_TIMESTAMP(var)
> @@ -1094,6 +1096,8 @@ PyEval_EvalFrameEx(PyFrameObject *f, int
>          }
>  #endif
>  
> +	DTRACE_PROBE1 (python, backtrace, PyEval_GetFrame());
> +	DTRACE_PROBE1 (python, get_variable, PyEval_GetFrame());
>  
>          /* Main switch on opcode */
>          READ_TIMESTAMP(inst0);

I'm sure that these will need to be transformed into properly autoconfed
additions to go upstream, like the function probes are.

I don't think the abstraction is right though.  A probe point should
define an event that is occurring (a statement eval?), but these seem to
be describing what the handler will do.  That's a bit backwards.

I think rather, since your additions are defining interesting things to
*do*, that they should be presented as functions.  The @1/@2
parameterization can be function args, as can the frame pointer.  Then
if needed, add the PyEval_GetFrame() argument to the existing sdt probes
or some new eventful probe.

Then with refactored, more-general utility functions, we can write
specific example scripts that do as your specific probes are doing,
searching and printing vars, backtracing specific function calls, etc.

HTH,
Josh

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: python tapset
  2011-11-18  0:42 ` Josh Stone
@ 2011-11-22 20:26   ` Stan Cox
  0 siblings, 0 replies; 3+ messages in thread
From: Stan Cox @ 2011-11-22 20:26 UTC (permalink / raw)
  To: Josh Stone; +Cc: systemtap

[-- Attachment #1: Type: text/plain, Size: 2465 bytes --]


> I think rather, since your additions are defining interesting things to
> *do*, that they should be presented as functions.  The @1/@2
> parameterization can be function args, as can the frame pointer.  Then
> if needed, add the PyEval_GetFrame() argument to the existing sdt probes
> or some new eventful probe.
>
> Then with refactored, more-general utility functions, we can write
> specific example scripts that do as your specific probes are doing,
> searching and printing vars, backtracing specific function calls, etc.

Two new probes, "function_entry" and "function_exit", which are 
triggered when a function is called and when it returns, are now used to 
inspect python's internal state.   A function iterate_over_functions has 
been added to return successive frames on the stack.  This can be used 
to drive functions that do interesting things for a frame.  Two such 
functions, backtrace and get_variable are provided.  A sample script to 
do backtracing and get variable values is shown.

stap -I $(pwd)/tapset -g -c '/usr/local/bin/python  ./celsius.py 30' 
celsius-bt.stp
30 Celsius  is 86 Farenheit
#3 <module>  at ./celsius.py:3
#0 <module>  at ./celsius.py:3
#1 main  at ./celsius.py:19
#0 main  at ./celsius.py:19
#1 <module>  at ./celsius.py:3
#2 celsius_to_farenheit (celsius:int ) at ./celsius.py:7
#0 celsius_to_farenheit (celsius:int ) at ./celsius.py:7
#1 main  at ./celsius.py:19
#2 <module>  at ./celsius.py:3

stap -I $(pwd)/tapset -g -c '/usr/local/bin/python ./celsius.py 30' 
celsius-var.stp '*'
30 Celsius  is 86 Farenheit
global str __builtins__ in <module> at ./celsius.py
global str __name__ in <module> at ./celsius.py = "__main__"
global str __file__ in <module> at ./celsius.py = "./celsius.py"
arg1 int celsius in celsius_to_farenheit at ./celsius.py = 30
tuple atuple in celsius_to_farenheit at ./celsius.py =  "a", "b", "c",
list alist in celsius_to_farenheit at ./celsius.py = [ 1, 2, 3,]
set aset in celsius_to_farenheit at ./celsius.py = { 1, 2, 3,}
dict adict in celsius_to_farenheit at ./celsius.py = { 1:"a" 2:"b" 3:"c" }
int nine in celsius_to_farenheit at ./celsius.py = 9
int five in celsius_to_farenheit at ./celsius.py = 5
int thirty_two in celsius_to_farenheit at ./celsius.py = 32
int i in celsius_to_farenheit at ./celsius.py = 1

stap -I $(pwd)/tapset -g -c '/usr/local/bin/python ./celsius.py 30' 
celsius-var.stp 'ni*'
30 Celsius  is 86 Farenheit
int nine in celsius_to_farenheit at ./celsius.py = 9

[-- Attachment #2: celsius-bt.stp --]
[-- Type: text/plain, Size: 292 bytes --]

probe process ("/usr/local/bin/python").mark ("function_entry") 
{
    frame = $arg1
    %( $# >= 1 %?
       skip_name = @1
       %:
       skip_name = "/lib/"
       %)
    for (i = 0; frame != 0; i++) {
        backtrace (frame, skip_name)
	frame = iterate_over_frames (frame, i)
    }
}

[-- Attachment #3: celsius-var.stp --]
[-- Type: text/plain, Size: 763 bytes --]

probe process ("/usr/local/bin/python").mark ("function_entry") 
{
    frame = $arg1
    %( $# >= 1 %?
       variable = @1
       %:
       variable = "*"
       %)
    %( $# >= 2 %?
       skip_name = @2
       %:
       skip_name = "/lib/"
       %)
    for (i = 0; frame != 0; i++) {
	get_variable (frame, 1, variable, skip_name)
	frame = iterate_over_frames (frame, i)
    }
}

probe process ("/usr/local/bin/python").mark ("function_exit")
{
    frame = $arg1
    %( $# >= 1 %?
       variable = @1
       %:
       variable = "*"
       %)
    %( $# >= 2 %?
       skip_name = @2
       %:
       skip_name = "/lib/"
       %)
    for (i = 0; frame != 0; i++) {
	get_variable (frame, 0, variable, skip_name)
	frame = iterate_over_frames (frame, i)
    }
}

[-- Attachment #4: ceval.patch --]
[-- Type: text/plain, Size: 750 bytes --]

--- ../../python/src/Python/ceval.c.orig	2011-11-17 15:54:06.287304375 -0500
+++ ../../python/src/Python/ceval.c	2011-11-21 15:51:40.492031048 -0500
@@ -19,6 +19,8 @@
 
 #include <ctype.h>
 
+#include <sys/sdt.h>
+
 #ifndef WITH_TSC
 
 #define READ_TIMESTAMP(var)
@@ -876,6 +878,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int
 
     tstate->frame = f;
 
+    DTRACE_PROBE1 (python, function_entry, PyEval_GetFrame());
     if (tstate->use_tracing) {
         if (tstate->c_tracefunc != NULL) {
             /* tstate->c_tracefunc, if defined, is a
@@ -3003,6 +3007,7 @@ fast_yield:
 
     /* pop frame */
 exit_eval_frame:
+    DTRACE_PROBE1 (python, function_exit,  PyEval_GetFrame());
     Py_LeaveRecursiveCall();
     tstate->frame = f->f_back;
 

[-- Attachment #5: python.stp --]
[-- Type: text/plain, Size: 10672 bytes --]

# Global Python variables

global gp_vars_seen			# which vars we have seen
global gp_dict_key = 0			# set by get_dict_item
global gp_dict_value = 0
global gp_dict_hash = 0
global gp_frame_n = 0			# set by iterate_over_frames

# FUNCTION GET_TYPE
# OBJ: PyObject
# RETURNS: string representation of type

function get_type:string (obj) {
    if (obj == 0)
	return ""
    ob_type = @cast (obj, "PyObject", "/usr/local/bin/python")->ob_type
    if (ob_type == 0)
	return ""
    ob_type_name = @cast (ob_type, "PyTypeObject", "/usr/local/bin/python")->tp_name
    return set_string (ob_type_name)
}

# FUNCTION GET_NAME
# F_CODE: The python frame code whose filename we are retrieving

function get_filename (f_code)
{
    co_filename = @cast (f_code, "PyCodeObject", "/usr/local/bin/python")->co_filename;
    co_filename_s = @cast (co_filename, "PyStringObject", "/usr/local/bin/python")->ob_sval;
    return set_string (co_filename_s)
}


# FUNCTION GET_NAME
# F_CODE: The python frame code whose function name we are retrieving

function get_name (f_code)
{
    co_name = @cast (f_code, "PyCodeObject", "/usr/local/bin/python")->co_name;
    co_name_s = @cast (co_name, "PyStringObject", "/usr/local/bin/python")->ob_sval;
    return co_name_str = set_string (co_name_s)
}

# FUNCTION SET_STRING
# STR_P: pointer to string
# RETURNS: string as a stap string

function set_string (str_p) {
    if (str_p == 0)
	return ""
    else
	return user_string (str_p)
}

# FUNCTION GET_SEQUENCE_ITEM
# TUPLE: array of PyObject
# I: index to retrieve 
# SEQ_TYPE: PyTupleObject or PyListObject
# RETURNS: array entry PyObject

function get_sequence_item (tuple, i, seq_type) {
    if (seq_type == "tuple")
	ob_items = @cast (tuple, "PyTupleObject", "/usr/local/bin/python")->ob_item;
    else if (seq_type == "list")
	ob_items = @cast (tuple, "PyListObject", "/usr/local/bin/python")->ob_item;

    ob_item = user_long (ob_items + (i * %{ sizeof (intptr_t) %}))
    return ob_item
}

# FUNCTION DISPLAY_VALUE
# VALUE: PyObject
# PREFIX: prefix string to display
# SUFFIX: suffix string to display

function display_value (value, prefix, suffix) {
    if (value == 0) {
	return 0
    }
    value_type_str = get_type (value)
    if (value_type_str == "str") {
	value_s = @cast (value, "PyStringObject", "/usr/local/bin/python")->ob_sval;
	value_str = set_string (value_s)
	printf ("%s\"%s\"%s", prefix, value_str, suffix)
    }
    else if (value_type_str == "int") {
	arg_value_int = @cast (value, "PyIntObject", "/usr/local/bin/python")->ob_ival;
	printf ("%s%d%s", prefix, arg_value_int, suffix)
    }
    else if (value_type_str == "tuple" || value_type_str == "list") {
	n = @cast (value, "PyTupleObject", "/usr/local/bin/python")->ob_size;
	if (value_type_str == "list") printf (" = [")
	else printf (" = ")
	for (i = 0; i < n; i++) 
	    display_value (get_sequence_item (value, i, value_type_str), " ", ",")
	if (value_type_str == "list") printf ("]")
    }
    else if (value_type_str == "set") {
	printf (" = {")
	n = @cast (value, "PySetObject", "/usr/local/bin/python")->used;
	for (i = 0; i <= n; i++)
	    display_value (get_set_item (value, i), " ", ",")
	printf ("}")
    }
    else if (value_type_str == "dict") {
	printf (" = {")
	n = @cast (value, "PyDictObject", "/usr/local/bin/python")->ma_used;
	for (i = 0; i <= n; i++) {
	    get_dict_item (value, i)      
	    if (gp_dict_hash == 0 && gp_dict_key == 0)
		continue
	    display_value (gp_dict_key, " ", ":")
	    display_value (gp_dict_value, "", "")
	}
	printf (" }")
    }
}

# FUNCTION GET_SET_ITEM
# SET: PySetObject
# I: set index to retrieve
# RETURNS: set entry PyObject

function get_set_item (set, i) {
    entries = @cast (set, "PySetObject", "/usr/local/bin/python")->table;
    n = @cast (set, "PySetObject", "/usr/local/bin/python")->used;
    if (i > n)
	return 0
  
    return @cast (entries, "setentry", "/usr/local/bin/python")[i]->key
}

# FUNCTION GET_DICT_ITEM
# DICT: PySetObject
# I: set index to retrieve
# RETURNS: sets the global variables GP_DICT_KEY, GP_DICT_VALUE, GP_DICT_HASH

function get_dict_item (dict, i) {
    entries = @cast (dict, "PyDictObject", "/usr/local/bin/python")->ma_table;
    n = @cast (dict, "PyDictObject", "/usr/local/bin/python")->ma_used;
    if (i > n)
	return 0
  
    gp_dict_hash = @cast (entries, "PyDictEntry", "/usr/local/bin/python")[i]->me_hash
    gp_dict_key = @cast (entries, "PyDictEntry", "/usr/local/bin/python")[i]->me_key
    gp_dict_value = @cast (entries, "PyDictEntry", "/usr/local/bin/python")[i]->me_value;
}

# FUNCTION DISPLAY_DICT_VARIABLE
# DICT: local or global variable name dictionary
# VARIABLE: variable to retrieve
# NAMESPACE: local or global

function display_dict_variable (dict, variable, namespace, co_name_str, co_filename_str) {
    n = @cast (dict, "PyDictObject", "/usr/local/bin/python")->ma_used;
    for (i = 0; i < n; i++) {
	get_dict_item (dict, i)      
	key_str = set_string (@cast (gp_dict_key, "PyStringObject", "/usr/local/bin/python")->ob_sval)
	key_type_str = get_type (gp_dict_key)

	if (gp_dict_key == 0 || gp_dict_value == 0) break
	if (wildcard_match (key_str, variable)) {
	    foreach ([var, file] in gp_vars_seen) {
		if (var == key_str
		    && file == co_filename_str)
		    return 0
	    }
	    printf ("%s %s %s", namespace, key_type_str, key_str)
	    printf (" in %s at %s", co_name_str, co_filename_str)
	    display_value (gp_dict_value, " = ", " ")
	    printf ("\n")
	    gp_vars_seen[key_str, co_filename_str] = co_name_str
	}
    }
}

# FUNCTION WILDCARD_MATCH
# STR: String to check for a wildcard
# PATTERN: The wildcard pattern.  Wildcard special characters are limited to '*'

function wildcard_match (str, pattern) {
    if (pattern == "*")
	return 1

    # Does pattern start at the beginning of str or is it '*'?
    if (substr (pattern, 0, 1) != "*" && substr (str, 0, 1) != substr (pattern, 0, 1))
	return 0

    hunk = tokenize (pattern,"*")
    while (hunk != "")
    {
	# find pattern hunk in str
	if (isinstr (str, hunk) == 0) {
	    return 0
	}
	match = 0
	for (i = 0; i < strlen (str) - strlen (hunk) + 1; i++) {
	    if (hunk == substr (str, i, strlen (hunk))) {
		match = 1
		break
	    }
	}	
	if (!match) return 0
	    
	# skip past hunk in str
	str = substr (str, i, strlen (str)-i+1)
	hunk = tokenize ("","*")
    }

    # Does pattern end at the end of str or is it '*'?
    if (substr (pattern, strlen (pattern)-1, 1) != "*"
	&& substr (str, strlen (str)-1, 1) != substr (pattern, strlen (pattern)-1, 1))
	return 0
    else
	return 1
}

# FUNCTION ITERATE_OVER_FRAMES
# FRAME: Pointer to a stack frame
# FRAME_N: The frame number of this stack frame

function iterate_over_frames (frame, frame_n)
{
    if (frame_n != 0) {
	gp_frame_n += 1
	frame =  @cast (frame, "struct _frame", "/usr/local/bin/python")->f_back
    }
    else
	gp_frame_n = 0
    return frame
}

    
# FUNCTION BACKTRACE
# FRAME: Pointer to first stack frame
# SKIP_NAME: Pathnames containing this string are ignored.  Default is "/lib/" to
#     ignore system python libraries

function backtrace (frame, skip_name)
{
    if (skip_name == "")
	skip_name = "/lib/"

    f_code = @cast (frame, "PyFrameObject", "/usr/local/bin/python")->f_code;
    co_filename_str = get_filename (f_code)
    if (isinstr (co_filename_str, skip_name) == 1)
	return 0
    co_name_str = get_name (f_code)
    co_firstlineno = @cast (f_code, "PyCodeObject", "/usr/local/bin/python")->co_firstlineno;

    printf ("#%d %s ", gp_frame_n, co_name_str)

    co_varnames = @cast (f_code, "PyCodeObject", "/usr/local/bin/python")->co_varnames;
    co_argcount = @cast (f_code, "PyCodeObject", "/usr/local/bin/python")->co_argcount;
    f_localsplus = @cast (frame, "struct _frame", "/usr/local/bin/python")->f_localsplus;
    for (i = 0; i < co_argcount; i++) {
	if (i == 0) print ("(");
	arg_name_str = user_string (@cast (get_sequence_item (co_varnames, i, "tuple"), "PyStringObject", "/usr/local/bin/python")->ob_sval)
	arg_value = user_long (f_localsplus + (i * %{ sizeof (intptr_t) %}))
	arg_type_name_str = get_type (arg_value)
	printf ("%s:%s ", arg_name_str, arg_type_name_str)
    }

    if (co_argcount) printf (")");

    printf (" at %s:%d\n", co_filename_str, co_firstlineno)
}
    
# FUNCTION GET_VARIABLE
# FRAME: Pointer to first stack frame
# MATCH_ARGS: Match function arguments
# VARIABLE: Variable wildcard to be matched.  Default is "*" to match all variables.
# SKIP_NAME: Pathnames containing this string are ignored.  Default is "/lib/" to
#     ignore system python libraries

function get_variable (frame, match_args, variable, skip_name)
{
    if (skip_name == "")
	skip_name = "/lib/"
    f_code = @cast (frame, "PyFrameObject", "/usr/local/bin/python")->f_code;
    co_filename_str = get_filename (f_code)
    if (isinstr (co_filename_str, skip_name) == 1)
	return 0
    co_name = @cast (f_code, "PyCodeObject", "/usr/local/bin/python")->co_name;
    co_name_s = @cast (co_name, "PyStringObject", "/usr/local/bin/python")->ob_sval;
    co_name_str = set_string (co_name_s)
    
    f_globals = @cast (frame, "struct _frame", "/usr/local/bin/python")->f_globals;
    if (f_globals != 0) {
	display_dict_variable (f_globals, variable, "global", co_name_str, co_filename_str)
    }
    f_locals = @cast (frame, "struct _frame", "/usr/local/bin/python")->f_locals;
    if (f_locals != 0) {
	display_dict_variable (f_locals, variable, "local", co_name_str, co_filename_str)
    }

    co_varnames = @cast (f_code, "PyCodeObject", "/usr/local/bin/python")->co_varnames;
    co_argcount = @cast (f_code, "PyCodeObject", "/usr/local/bin/python")->co_argcount;
    f_localsplus = @cast (frame, "struct _frame", "/usr/local/bin/python")->f_localsplus;
    ob_size = @cast (co_varnames, "PyTypeObject", "/usr/local/bin/python")->ob_size;
    # iterate through the local variable list
    for (i = 0; i < ob_size; i++) {
	arg_name_str = user_string (@cast (get_sequence_item (co_varnames, i, "tuple"), "PyStringObject", "/usr/local/bin/python")->ob_sval)
	if (! wildcard_match (arg_name_str, variable))
	    continue
	arg_value = user_long (f_localsplus + (i * %{ sizeof (intptr_t) %}))
	arg_type_name_str = get_type (arg_value)
	next_var = 0
	foreach ([var, file] in gp_vars_seen)
	if (var == arg_name_str && file == co_filename_str)
	    next_var = 1
	if (next_var || arg_type_name_str == "")
	    continue
	if (i < co_argcount && match_args)
	    printf ("arg%d ", i + 1)
	gp_vars_seen[arg_name_str, co_filename_str] = co_name_str
	printf ("%s %s", arg_type_name_str, arg_name_str)
	printf (" in %s at %s", co_name_str, co_filename_str)
	display_value (arg_value, " = ", " ")
	printf ("\n")
    }
}

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2011-11-22 20:26 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-11-17 21:53 python tapset Stan Cox
2011-11-18  0:42 ` Josh Stone
2011-11-22 20:26   ` Stan Cox

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).