The Digital UNIX Kernel Debugging Tools subset must be installed on your system before you can create custom extensions to the kdbx debugger. This subset contains header files and libraries needed for building kdbx extensions. See Section 3.1 for more information.
Identify the kernel variables and symbols that you need to examine.
Display the information so that anyone who needs to use it can read
and understand it.
As with any good program, it is important to provide informational error
messages in the extension.
Before you
write an extension, become familiar with the library routines in the libkdbx.a library. These library routines provide convenient methods
of extracting and displaying kernel data. The routines are declared in the /usr/include/kdbx.h header file and described in Section 3.2.
You should also study the extensions that are provided on your system
in the /var/kdbx directory. These extensions and the example extensions
discussed in Section 3.3 can help you understand
what is involved in writing an extension and provide good examples of using
the kdbx library functions.
The sections that follow describe the special data types defined for
use in kdbx extensions and the library routines you use in extensions.
The library routine descriptions show the routine syntax and describe the
routine arguments. Examples included in the descriptions show significant
lines in boldface type.
The following is the type definition for the StatusType data
type:
The following is the type definition for the Status data
type:
The values in comm and local provide the error
code interpreted by print_status.
The following is the type definition for the FieldRec data
type:
The char *name declaration is the name of the
field in question. The int type declaration is the
type of the field, for example, NUMBER, STRUCTURE, POINTER. The caddr_t data and char *error declarations are
initially set to NULL. The read_field_vals function fills in these
values.
The following is the type definition for the DataStruct data
type:
This function has the following syntax:
char *addr_to_proc(long addr );
3.1 Basic Considerations for Writing Extensions
Before writing an extension, consider the following:
3.2 Standard kdbx Library Functions
The kdbx debugger
provides a number of library functions that are used by the resident extensions.
You can use these functions, which are declared in the ./usr/include/kdbx.h header file, to develop customized extensions for your application.
To use the functions, you must include the ./usr/include/kdbx.h
header file in your extension.3.2.1 Special kdbx Extension Data Types
The routines described in this
section use the following special data types: StatusType, Status, FieldRec, and DataStruct. The uses of
these data types are as follows:
typedef enum { OK, Comm, Local } StatusType;
typedef struct {
StatusType type;
union {
int comm;
int local;
} u;
} Status;
typedef struct {
char *name;
int type;
caddr_t data;
char *error;
} FieldRec;
typedef long DataStruct;
3.2.2 Converting an Address to a Procedure Name
The addr_to_proc function returns
the name of the procedure that begins the address you pass to the function.
If the address is not the beginning of a procedure, then a string representation
of the address is returned. The return value is dynamically allocated by malloc and should be freed by the extension when it is no longer needed.
Argument | Input/Output | Description |
---|---|---|
addr | Input | Specifies the address that you want converted to a procedure name |
For example:
conf1 = addr_to_proc((long) bus_fields[3].data); conf2 = addr_to_proc((long) bus_fields[4].data); sprintf(buf, "Config 1 - %sConfig 2 - %s", conf1, conf2); free(conf1); free(conf2);
DataStruct array_element(DataStruct sym , int i , char ** error );
3.2.3 Getting a Representation of an Array Element
The array_element function returns
a representation of one element of an array. The function returns non-NULL
if it succeeds or NULL if an error occurs. When the value of error is non-NULL,
the error argument is set to point to the error message. This function
has the following syntax:
Argument | Input/Output | Description |
---|---|---|
sym | Input | Names the array |
i | Input | Specifies the index of the element |
error | Output | Returns a pointer to an error message, if the return value is NULL |
You usually use the array_element function with the read_field_vals function. You use the array_element function to get a representation of an array element that is a structure or pointer to a structure. You then pass this representation to the read_field_vals function to get the values of fields inside the structure. For an example of how this is done, see Example 3-4 in Section 3.3.
The first argument of the array_element function is usually the result returned from the read_sym function.
The read_sym, array_element, and read_field_vals functions are often used together to retrieve the values of an array of structures pointed to by a global pointer. (For more information about using these functions, see the description of the read_sym function in Section 3.2.27.) Note
For example:
if((ele = array_element(sz_softc, cntrl, &error)) == NULL){ fprintf(stderr, "Couldn't get %d'th element of sz_softc:\n, cntrl"); fprintf(stderr, "%s\n", error); }
This function returns TRUE if it is successful, FALSE otherwise. When
the return value is FALSE, an error message is returned in an argument to
the function.
This function has the following syntax:
Boolean array_element_val(DataStruct sym , int i , long * ele_ret , char
** error );
3.2.4 Retrieving an Array Element Value
The array_element_val function
returns the value of an array element. It returns the integer value if the
data type of the array element is an integer data type. It returns the pointer
value if the data type of the array element is a pointer data type.
Argument | Input/Output | Description |
---|---|---|
sym | Input | Names the array |
i | Input | Specifies the index of the element |
ele_ret | Output | Returns the value of the pointer |
error | Output | Returns a pointer to an error message if the return value is FALSE |
You use the array_element_val function when the array element is of a basic C type. You also use this function if the array element is of a pointer type and the pointer value is what you actually want. This function returns a printable value. The first argument of the array_element_val function usually comes from the returned result of the read_sym function.
For example:
static char get_ele(array, i) DataStruct array; int i; { char *error, ret; long val; if(!array_element_val(array, i, &val, &error)){ fprintf(stderr, "Couldn't read array element:\n"); fprintf(stderr, "%s\n", error); quit(1); } ret = val; return(ret); }
unsigned int array_size(DataStruct sym , char**error );
3.2.5 Returning the Size of an Array
The array_size function returns
the size of the specified array. This function has the following syntax:
Argument | Input/Output | Description |
---|---|---|
sym | Input | Names the array |
error | Output | Returns a pointer to an error message if the return value is non-NULL |
For example:
busses = read_sym("bus_list"); if((n = array_size(busses, &error)) == -1){ fprintf(stderr, "Couldn't call array_size:\n"); fprintf(stderr, "%s\n", error); quit(1); }
Boolean cast(long addr, char * type, DataStruct * ret_type, char ** error);
3.2.6 Casting a Pointer to a Data Structure
The cast
function casts the pointer to a structure as a structure data type and returns
the structure. This function has the following syntax:
Argument | Input/Output | Description |
---|---|---|
addr | Input | Specifies the address of the data structure you want returned |
type | Input | Specifies the datatype of the data structure |
ret_type | Output | Returns the name of the data structure |
error | Output | Returns a pointer to an error message if the return value is FALSE |
You usually use the cast function with the read_field_vals function. Given the address of a structure, you call the cast function to convert the pointer from the type long to the type DataStruct. Then, you pass the result to the read_field_vals function, as its first argument, to retrieve the values of data fields in the structure.
For example:
if(!cast(addr, "struct file", &fil, &error)){ fprintf(stderr, "Couldn't cast address to a file:\n"); fprintf(stderr, "%s\n", error); quit(1); }
This function has the following syntax:
void check_args(int argc, char **
argv, char * help_string);
3.2.7 Checking Arguments Passed to an Extension
The check_args function checks the arguments passed to an extension or displays
a help message. The function displays a help message when the user specifies
the -help flag on the command line.
Argument | Input/Output | Description |
---|---|---|
argc | Input | Passes in the first argument to the command |
argv | Input | Passes in the second argument to the command |
help_string | Input | Specifies the help message to be displayed to the user |
For example:
check_args(argc, argv, help_string); if(!check_fields("struct sz_softc", fields, NUM_FIELDS, NULL)){ field_errors(fields, NUM_FIELDS); quit(1); }
This function has the following syntax:
Boolean check_fields(char * symbol, FieldRec
* fields, int nfields, char
** hints);
3.2.8 Checking the Fields in a Structure
The check_fields function verifies
that the specified function consists of the expected number of fields and
that those fields have the correct data type. If the function is successful,
TRUE is returned; otherwise, the error parts of the affected fields are filled
in with errors, and FALSE is returned.
Argument | Input/Output | Description |
---|---|---|
symbol | Input | Names the structure to be checked |
fields | Input | Describes the fields to be checked |
nfields | Input | Specifies the size of the fields argument |
hints | Input | Unused and should always be set to NULL |
You should check the structure type using the check_fields function before using the read_field_vals function to read field values.
For example:
FieldRec fields[] = { { ".sc_sysid", NUMBER, NULL, NULL }, { ".sc_aipfts", NUMBER, NULL, NULL }, { ".sc_lostarb", NUMBER, NULL, NULL }, { ".sc_lastid", NUMBER, NULL, NULL }, { ".sc_active", NUMBER, NULL, NULL } }; check_args(argc, argv, help_string); if(!check_fields("struct sz_softc", fields, NUM_FIELDS, NULL)){ field_errors(fields, NUM_FIELDS); quit(1); }
This function has the following syntax:
void context(Boolean user);
3.2.9 Setting the kdbx Context
The context function sets the context
to user context or proc context. If the context is
set to the user context, aliases defined in the extension affect
user aliases.
Argument | Input/Output | Description |
---|---|---|
user | Input | Sets the context to user if TRUE or proc if FALSE |
For example:
if(head) print(head); context(True); for(i=0;i<len;i++){
.
.
.
void dbx(char * command, Boolean expect_output);
3.2.10 Passing Commands to the dbx Debugger
The dbx function passes a command to the dbx debugger. The function
has an argument, expect_output, that controls when it returns.
If you set the expect_output argument to TRUE, the function returns
after the command is sent, and expects the extension to read the output from dbx. If you set the expect_output argument to FALSE, the
function waits for the command to complete execution, reads the acknowledgement
from kdbx, and then returns.
Argument | Input/Output | Description |
---|---|---|
command | Input | Specifies the command to be passed to dbx |
expect_output | Input | Indicates whether the extension expects output and determines when the function returns |
For example:
dbx(out, True); if((buf = read_response(&status)) == NULL){ print_status("main", &status); quit(1); } else { process_buf(buf); quit(0); }
This function has the following syntax:
DataStruct deref_pointer(DataStruct data);
3.2.11 Dereferencing a Pointer
The deref_pointer function returns
a representation of the object pointed to by a pointer. The function displays
an error message if the data argument passed is not a valid address.
Argument | Input/Output | Description |
---|---|---|
data | Input | Names the data structure that is being dereferenced |
structure = deref_pointer(struct_pointer);
void field_errors(FieldRec * fields, int nfields);
3.2.12 Displaying the Error Messages Stored in Fields
The field_errors function displays
the error messages stored in fields by the check_fields function.
This function has the following syntax:
Argument | Input/Output | Description |
---|---|---|
fields | Input | Names the fields that contain the error messages |
nfields | Input | Specifies the size of the fields argument |
For example:
if(!read_field_vals(proc, fields, NUM_FIELDS)){ field_errors(fields, NUM_FIELDS); return(False); }
extern char *format_addr(long addr, char
* buffer);
3.2.13 Converting a Long Address to a String Address
The format_addr function converts
a 64-bit address of type long into a 32-bit address of type char. This function has the following syntax:
Argument | Input/Output | Description |
---|---|---|
addr | Input | Specifies the address to be converted |
buffer | Output | Returns the converted address and must be at least 12 characters long |
Use this function to save space on the output line. For example, the 64-bit address 0xffffffff12345678 is converted into v0x12345678.
For example:
static Boolean prfile(DataStruct ele, long vn_addr, long socket_addr) { char *error, op_buf[12], *ops, buf[256], address[12], cred[12], data[12]; if(!read_field_vals(ele, fields, NUM_FIELDS)){ field_errors(fields, NUM_FIELDS); return(False); } if((long) fields[1].data == 0) return(True); if((long) (fields[5].data) == 0) ops = " *Null* "; else if((long) (fields[5].data) == vn_addr) ops = " vnops "; else if((long) (fields[5].data) == socket_addr) ops = " socketops "; else format_addr((long) fields[5].data, op_buf); format_addr((long) struct_addr(ele), address); format_addr((long) fields[2].data, cred); format_addr((long) fields[3].data, data); sprintf(buf, "%s %s %4d %4d %s %s %s %6d %s%s%s%s%s%s%s%s%s", address, get_type((int) fields[0].data), fields[1].data, fields[2].data, ops, cred, data, fields[6].data, ((long) fields[7].data) & FREAD ? " read" : , ((long) fields[7].data) & FWRITE ? " write" : , ((long) fields[7].data) & FAPPEND ? " append" : , ((long) fields[7].data) & FNDELAY ? " ndelay" : , ((long) fields[7].data) & FMARK ? " mark" : , ((long) fields[7].data) & FDEFER ? " defer" : , ((long) fields[7].data) & FASYNC ? " async" : , ((long) fields[7].data) & FSHLOCK ? " shlck" : , ((long) fields[7].data) & FEXLOCK ? " exlck" : ); print(buf); return(True); }
void free_sym(DataStruct sym);
3.2.14 Freeing Memory
The free_sym function releases the
memory held by a specified symbol. This function has the following syntax:
Argument | Input/Output | Description |
---|---|---|
sym | Input | Names the symbol that is using memory that can be freed |
For example:
free_sym(rec->data);
This function has the following syntax:
void krash(char * command, Boolean
quote, Boolean expect_output);
3.2.15 Passing Commands to the kdbx Debugger
The krash function passes a command
to kdbx for execution. You specify the command you want passed
to kdbx as the first argument to the krash function.
The second argument allows you to pass quotation marks (""),
apostrophes ('), and backslash characters (\) to kdbx. The function has an argument, expect_output, which controls when it returns. If you set the expect_output argument to TRUE, the function returns after the command is sent, and
expects the extension to read the output from dbx. If you set the expect_output argument to FALSE, the function waits for the command
to complete execution, reads the acknowledgement from kdbx, and
then returns.
Argument | Input/Output | Description |
---|---|---|
command | Input | Names the command to be executed |
quote | Input | If set to TRUE causes the quote character, apostrophe, and backslash to be appropriately quoted so that they are treated normally, instead of as special characters |
expect_output | Input | Indicates whether the extension expects output and determines when the function returns |
For example:
do { : if(doit){ format(command, buf, type, addr, last, i, next); context(True); krash(buf, False, True); while((line = read_line(&status)) != NULL){ print(line); free(line); } : addr = next; i++;Suppose the preceding example is used to list the addresses of each node in the system mount table, which is a linked list. The following list describes the arguments to the format function in this case:
p ((struct mount *) 0xffffffff8196db30).m_next
Boolean list_nth_cell(long addr, char * type, int n,char * next_field, Boolean do_check, long * val_ret, char ** error );
3.2.16 Getting the Address of an Item in a Linked List
The list_nth_cell function returns
the address of one of the items in a linked list. This function has the following
format:
Argument | Input/Output | Description |
---|---|---|
addr | Input | Specifies the starting address of the linked list |
type | Input | Specifies the data type of the item for which you are requesting an address |
n | Input | Supplies a number indicating which list item's address is being requested |
next_field | Input | Gives the name of the field that points to the next item in the linked list |
do_check | Input | Determines whether kdbx checks the arguments to ensure that correct information is being sent (TRUE setting) |
val_ret | Output | Returns the address of the requested list item |
error | Output | Returns a pointer to an error message if the return value is FALSE |
For example:
long root_addr, addr; if (!read_sym_val("rootfs", NUMBER, &root_addr, &error)){
.
.
.
} if(!list_nth_cell(root_addr, "struct mount", i, "m_next", True, &addr, &error)){ fprintf(stderr, "Couldn't get %d'th element of mount table\n", i); fprintf(stderr, "%s\n", error); quit(1); }
This function has the following syntax:
void new_proc(char * args, char **
output_ret);
3.2.17 Passing an Extension to kdbx
The new_proc function directs kdbx to execute a proc command with arguments specified in args. The args arguments can name a Digital-supplied extension
or an extension that you create.
Argument | Input/Output | Description |
---|---|---|
args | Input | Names the extensions to be passed to kdbx |
output_ret | Output | Returns the output from the extension, if it is non-NULL |
For example:
static void prmap(long addr) { char cast_addr[36], buf[256], *resp; sprintf(cast_addr, "((struct\ vm_map_t\ *)\ 0x%p)", addr); sprintf(buf, "printf cast_addr); new_proc(buf, &resp); print(resp); free(resp); }
This function has the following syntax:
Boolean next_number(char * buf, char **
next, long * ret);
3.2.18 Getting the Next Token as an Integer
The next_number function converts
the next token in a buffer to an integer. The function returns TRUE if successful,
or FALSE if there was an error.
Argument | Input/Output | Description |
---|---|---|
buf | Input | Names the buffer containing the value to be converted |
next | Output | Returns a pointer to the next value in the buffer, if that value is non-NULL |
ret | Output | Returns the integer value |
For example:
resp = read_response_status(); next_number(resp, NULL, &size); ret->size = size;
char *next_token(char * ptr, int *
len_ret, char ** next_ret);
3.2.19 Getting the Next Token as a String
The next_token function returns
a pointer to the next token in the specified pointer to a string. A token
is a sequence of nonspace characters. This function has the following syntax:
Argument | Input/Output | Description |
---|---|---|
ptr | Input | Specifies the name of the pointer |
len_ret | Output | Returns the length of the next token, if non-NULL |
next_ret | Output | Returns a pointer to the first character after, but not included in the current token, if non-NULL |
You use this function to extract words or other tokens from a character string. A common use, as shown in the example that follows, is to extract tokens from a string of numbers. You can then cast the tokens to a numerical data type, such as the long data type, and use them as numbers.
For example:
static long *parse_memory(char *buf, int offset, int size) { long *buffer, *ret; int index, len; char *ptr, *token, *next; NEW_TYPE(buffer, offset + size, long, long *, "parse_memory"); ret = buffer; index = offset; ptr = buf; while(index < offset + size){ if((token = next_token(ptr, &len, &next)) == NULL){ ret = NULL; break; } ptr = next; if(token[len - 1] == ':') continue; buffer[index] = strtoul(token, &ptr, 16); if(ptr != &token[len]){ ret = NULL; break; } index++; } if(ret == NULL) free(buffer); return(ret); }
The print function automatically displays a newline character
at the end of the output, it fails if it detects a newline character at the
end of the buffer.
This function has the following format:
void print(char * message);
3.2.20 Displaying a Message
The print function displays a message
on the terminal screen. Because of the input and output redirection done by kdbx, all output to stdout from a kdbx extension
goes to dbx. As a result, a kdbx extension cannot use
normal C output functions such as printf and fprintf(stdout,...) to display information on the screen. Although the fprintf(stderr,...) function is still available, the recommended method is to first
use the sprintf function to print the output into a character buffer
and then use the kdbx library function print to display
the contents of the buffer to the screen.
Argument | Input/Output | Description |
---|---|---|
message | Input | The message to be displayed |
For example:
if(do_short){ if(!check_fields("struct mount", short_mount_fields, NUM_SHORT_MOUNT_FIELDS, NULL)){ field_errors(short_mount_fields, NUM_SHORT_MOUNT_FIELDS); quit(1); } print("SLOT MAJ MIN TYPE DEVICE MOUNT POINT"); }
void print_status(char *message, Status * status);
3.2.21 Displaying Status Messages
The print_status function displays
a status message that you supply and a status message supplied by the system.
This function has the following format:
Argument | Input/Output | Description |
---|---|---|
message | Input | Specifies the extension-defined status message |
status | Input | Specifies the status returned from another library routine |
For example:
if(status.type != OK){ print_status("read_line failed", &status); quit(1); }
void quit(int i);
3.2.22 Exiting from an Extension
The quit function sends a quit
command to kdbx. This function has the following format:
Argument | Input/Output | Description |
---|---|---|
i | Input | The status at the time of the exit from the extension |
For example:
if (!read_sym_val("vm_swap_head", NUMBER, &end, &error)) { fprintf(stderr, "Couldn't read vm_swap_head:\n"); fprintf(stderr, "%s\n", error); quit(1); }
This function has the following format:
Boolean read_field_vals(DataStruct data, FieldRec
*fields, int nfields);
3.2.23 Reading the Values in Structure Fields
The read_field_vals function
reads the value of fields in the specified structure. If this function is
successful, then the data parts of the fields are filled in and TRUE is returned;
otherwise, the error parts of the affected fields are filled in with errors
and FALSE is returned.
Argument | Input/Output | Description |
---|---|---|
data | Input | Names the structure that contains the field to be read |
fields | Input | Describes the fields to be read |
nfields | Input | Contains the size of the field array |
For example:
if(!read_field_vals(pager, fields, nfields)){ field_errors(fields, nfields); return(False); }
This function has the following format:
char *read_line(Status * status);
3.2.24 Returning a Line of kdbx Output
The read_line function returns the
next line of the output from the last kdbx command executed. If
the end of the output is reached, this function returns NULL and a status
of OK. If the status is something other than OK when the function returns
NULL, an error occurred.
Argument | Input/Output | Description |
---|---|---|
status | Output | Contains the status of the request, which is OK for successful requests |
For example:
while((line = read_line(&status)) != NULL){ print(line); free(line); }
This function has the following format:
Boolean read_memory(long start_addr, int n, char *buf, char ** error)
3.2.25 Reading an Area of Memory
The read_memory function reads
an area of memory starting at the address you specify and running for the
number of bytes you specify. The read_memory function returns TRUE
if successful and FALSE if there was an error.
Argument | Input/Output | Description |
---|---|---|
start_addr | Input | Specifies the starting address for the read |
n | Input | Specifies the number of bytes to read |
buf | Output | Returns the memory contents |
error | Output | Returns a pointer to an error message if the return value is FALSE |
You can use this function to look up any type of value, however it is most useful for retrieving the value of pointers that point to other pointers.
For example:
start_addr = (long) ((long *)utask_fields[7].data + i-NOFILE_IN_U); if(!read_memory(start_addr , sizeof(long *), (char *)&val1, &error) || !read_memory((long)utask_fields[8].data , sizeof(long *), (char *)&val2, &error)){ fprintf(stderr, "Couldn't read_memory\n"); fprintf(stderr, "%s\n", error); quit(1); }
This function has the following syntax:
char *read_response(Status * status);
3.2.26 Reading the Response to a kdbx Command
The read_response function reads
the response to the last kdbx command entered. If any errors occurred,
NULL is returned and the status argument is filled in.
Argument | Input/Output | Description |
---|---|---|
status | Output | Contains the status of the last kdbx command |
For example:
if(!*argv) Usage(); command = argv; if(size == 0){ sprintf(buf, "print sizeof(*((%s) 0))", type); dbx(buf, True); if((resp = read_response(&status)) == NULL){ print_status("Couldn't read sizeof", &status); quit(1); } size = strtoul(resp, &ptr, 0); if(ptr == resp){ fprintf(stderr, "Couldn't parse sizeof(%s):\n", type); quit(1); } free(resp); }
DataStruct read_sym(char * name);
3.2.27 Reading Symbol Representations
The read_sym function returns a representation
of the named symbol. This function has the following format:
Argument | Input/Output | Description |
---|---|---|
name | Input | Names the symbol, which is normally a pointer to a structure or an array of structures inside the kernel |
For example:
busses = read_sym("bus_list");
Boolean read_sym_addr(char * name, long * ret_val, char ** error);
3.2.28 Reading a Symbol's Address
The read_sym_addr function reads
the address of the specified symbol. This function has the following format:
Argument | Input/Output | Description |
---|---|---|
name | Input | Names the symbol for which an address is required |
ret_val | Output | Returns the address of the symbol |
error | Output | Returns a pointer to an error message when the return status is FALSE |
For example:
if(argc == 0) fil = read_sym("file"); if(!read_sym_val("nfile", NUMBER, &nfile, &error) || !read_sym_addr("vnops", &vn_addr, &error) || !read_sym_addr("socketops", &socket_addr, &error)){ fprintf(stderr, "Couldn't read nfile:\n"); fprintf(stderr, "%s\n", error); quit(1); }
Boolean read_sym_val(char * name, int
type, long * ret_val, char **
error);
3.2.29 Reading the Value of a Symbol
The read_sym_val function returns
the value of the specified symbol. This function has the following format:
Argument | Input/Output | Description |
---|---|---|
name | Input | Names the symbol for which a value is needed |
type | Input | Specifies the data type of the symbol |
ret_val | Output | Returns the value of the symbol |
error | Output | Returns a pointer to an error message when the status is FALSE |
You use the read_sym_val function to retrieve the value of a global variable. The value returned by the read_sym_val function has the type long, unlike the value returned by the read_sym function which has the type DataStruct.
For example:
if(argc == 0) fil = read_sym("file"); if(!read_sym_val("nfile", NUMBER, &nfile, &error) || !read_sym_addr("vnops", &vn_addr, &error) || !read_sym_addr("socketops", &socket_addr, &error)){ fprintf(stderr, "Couldn't read nfile:\n"); fprintf(stderr, "%s\n", error); quit(1); }
char *struct_addr(DataStruct data);
3.2.30 Getting the Address of a Data Representation
The struct_addr function returns
the address of a data representation. This function has the following format:
Argument | Input/Output | Description |
---|---|---|
data | Input | Specifies the structure for which an address is needed |
For example:
if(bus_fields[1].data != 0){ sprintf(buf, "Bus #%d (0x%p): Name - \"%s\"\tConnected to - \"%s\, i, struct_addr(bus), bus_fields[1].data, bus_fields[2].data); print(buf); sprintf(buf, "\tConfig 1 - %s\tConfig 2 - %s", addr_to_proc((long) bus_fields[3].data), addr_to_proc((long) bus_fields[4].data)); print(buf); if(!prctlr((long) bus_fields[0].data)) quit(1); print(); }
This function has the following format:
Boolean to_number(char * str, long *
val);
3.2.31 Converting a String to a Number
The to_number function converts
a string to a number. The function returns TRUE if successful, or FALSE if
conversion was not possible.
Argument | Input/Output | Description |
---|---|---|
str | Input | Contains the string to be converted |
val | Output | Contains the numerical equivalent of the string |
For example:
check_args(argc, argv, help_string); if(argc < 5) Usage(); size = 0; type = argv[1]; if(!to_number(argv[2], &len)) Usage(); addr = strtoul(argv[3], &ptr, 16); if(*ptr != '\0'){ if(!read_sym_val(argv[3], NUMBER, &addr, &error)){ fprintf(stderr, "Couldn't read %s:\n", argv[3]); fprintf(stderr, "%s\n", error); Usage(); } }
This cc command compiles an extension named test.c.
The kdbx.a library is linked with the extensions, as specified
by the -l flag. The output from this command is named test, as specified by the -o flag.
Once the extension compiles successfully, you should test it and, if
necessary, debug it as described in Section 3.5.
When the extension is ready for use, place it in a directory that is
accessible to other users. Digital UNIX extensions are located in the /var/kdbx directory.
The following example shows how to invoke the test extension
from within the kdbx debugger:
If you are using a workstation with two windows or have two terminals,
perform the following steps to set up your kdbx and dbx
debugging sessions:
Begin the kdbx session:
Begin the dbx session:
The file pipein directs output from the dbx session
to the kdbx session. The file pipeout directs output
from the kdbx session to the dbx session.
If you are using one terminal, perform the following steps to set up
your kdbx and dbx sessions:
3.3 Examples of kdbx Extensions
This section contains examples of the three types of extensions provided
by the kdbx debugger:
Example 3-1: Template Extension Using Lists
#include <stdio.h>
#include <kdbx.h>
static char *help_string =
"<Usage info goes here> \\\n\ (1)
";
FieldRec fields[] = {
{ ".<name of next field>", NUMBER, NULL, NULL }, (2)
<data fields>
};
#define NUM_FIELDS (sizeof(fields)/sizeof(fields[0]))
main(argc, argv)
int argc;
char **argv;
{
DataStruct head;
unsigned int next;
char buf[256], *func, *error;
check_args(argc, argv, help_string);
if(!check_fields("<name of list structure>", fields, NUM_FIELDS, NULL)){ (3)
field_errors(fields, NUM_FIELDS);
quit(1);
}
if(!read_sym_val("<name of list head>", NUMBER, (caddr_t *) &next, &error)){ (4)
fprintf(stderr, "%s\n", error);
quit(1);
}
sprintf(buf, "<table header>"); (5)
print(buf);
do {
if(!cast(next, "<name of list structure>", &head, &error)){ (6)
fprintf(stderr, "Couldn't cast to a <struct>:\n"); (7)
fprintf(stderr, "%s:\n", error);
}
if(!read_field_vals(head, fields, NUM_FIELDS)){
field_errors(fields, NUM_FIELDS);
break;
}
<print data in this list cell> (8)
next = (int) fields[0].data;
} while(next != 0);
quit(0);
}
Example 3-2: Extension That Uses Linked Lists: callout.c
#include <stdio.h>
#include <errno.h>
#include <kdbx.h>
#define KERNEL
#include <sys/callout.h>
static char *help_string =
"callout - print the callout table \\\n\
Usage : callout [cpu] \\\n\
";
FieldRec processor_fields[] = {
{ ".calltodo.c_u.c_ticks", NUMBER, NULL, NULL },
{ ".calltodo.c_arg", NUMBER, NULL, NULL },
{ ".calltodo.c_func", NUMBER, NULL, NULL },
{ ".calltodo.c_next", NUMBER, NULL, NULL },
{ ".lbolt", NUMBER, NULL, NULL },
{ ".state", NUMBER, NULL, NULL },
};
FieldRec callout_fields[] = {
{ ".c_u.c_ticks", NUMBER, NULL, NULL },
{ ".c_arg", NUMBER, NULL, NULL },
{ ".c_func", NUMBER, NULL, NULL },
{ ".c_next", NUMBER, NULL, NULL },
};
#define NUM_PROCESSOR_FIELDS
(sizeof(processor_fields)/sizeof(processor_fields[0]))
#define NUM_CALLOUT_FIELDS (sizeof(callout_fields)/sizeof(callout_fields[0]))
main(int argc, char **argv)
{
DataStruct processor_ptr, processor, callout;
long next, ncpus, ptr_val, i;
char buf[256], *func, *error, arg[13];
int cpuflag = 0, cpuarg = 0;
long headptr;
Status status;
char *resp;
if ( !(argc == 1 || argc == 2) ) {
fprintf(stderr, "Usage: callout [cpu]\n");
quit(1);
}
check_args(argc, argv, help_string);
if (argc == 2) {
cpuflag = 1;
errno = 0;
cpuarg = atoi(argv[1]);
if (errno != 0)
fprintf(stderr, "Invalid argument value for the cpu number.\n");
}
if(!check_fields("struct processor", processor_fields, NUM_PROCESSOR_FIELDS,
NULL)){
field_errors(processor_fields, NUM_PROCESSOR_FIELDS);
quit(1);
}
if(!check_fields("struct callout", callout_fields, NUM_CALLOUT_FIELDS, NULL)){
field_errors(callout_fields, NUM_CALLOUT_FIELDS);
quit(1);
}
/* This gives the same result as "(kdbx) p processor_ptr" */
if(!read_sym_addr("processor_ptr", &headptr, &error)){
fprintf(stderr, "%s\n", error);
quit(1);
}
/* get ncpus */
if(!read_sym_val("ncpus", NUMBER, &ncpus, &error)){
fprintf(stderr, "Couldn't read ncpus:\n");
fprintf(stderr, "%s\n", error);
quit(1);
}
for (i=0; i < ncpus; i++) {
/* if user wants only one cpu and this is not the one, skip */
if (cpuflag)
if (cpuarg != i) continue;
/* get the ith pointer (values) in the array */
sprintf(buf, "set $hexints=0");
dbx(buf, False);
sprintf(buf, "p \*(long \*)0x%lx", headptr+8*i);
dbx(buf, True);
if((resp = read_response(&status)) == NULL){
print_status("Couldn't read value of processor_ptr[i]:", &status);
quit(1);
}
ptr_val = strtoul(resp, (char**)NULL, 10);
free(resp);
if (! ptr_val) continue; /* continue if this slot is disabled */
if(!cast(ptr_val, "struct processor", &processor, &error)){
fprintf(stderr, "Couldn't cast to a processor:\n");
fprintf(stderr, "%s:\n", error);
quit(1);
}
if(!read_field_vals(processor, processor_fields, NUM_PROCESSOR_FIELDS)){
field_errors(processor_fields, NUM_PROCESSOR_FIELDS);
quit(1);
}
if (processor_fields[5].data == 0) continue;
print("");
sprintf(buf, "Processor: %10u", i);
print(buf);
sprintf(buf, "Current time (in ticks): %10u",
processor_fields[4].data ); /*lbolt*/
print(buf);
/* for first element, we are interested in time only */
print("");
sprintf(buf, " FUNCTION ARGUMENT TICKS(delta)");
print(buf);
print( "============================= ============ ============");
/* walk through the rest of the list */
next = (long) processor_fields[3].data;
while(next != 0) {
if(!cast(next, "struct callout", &callout, &error)){
fprintf(stderr, "Couldn't cast to a callout:\n");
fprintf(stderr, "%s:\n", error);
}
if(!read_field_vals(callout, callout_fields, NUM_CALLOUT_FIELDS)){
field_errors(callout_fields, NUM_CALLOUT_FIELDS);
break;
}
func = addr_to_proc((long) callout_fields[2].data);
format_addr((long) callout_fields[1].data, arg);
sprintf(buf, "%-32.32s %12s %12d", func, arg,
((long)callout_fields[0].data & CALLTODO_TIME) -
(long)processor_fields[4].data);
print(buf);
next = (long) callout_fields[3].data;
}
} /* end of for */
quit(0);
} /* end of main() */
Example 3-3: Template Extensions Using Arrays
#include <stdio.h>
#include <kdbx.h>
static char *help_string =
"<Usage info> \\\n\ (1)
";
FieldRec fields[] = {
<data fields> (2)
};
#define NUM_FIELDS (sizeof(fields)/sizeof(fields[0]))
main(argc, argv)
int argc;
char **argv;
{
int i, size;
char *error, *ptr;
DataStruct head, ele;
check_args(argc, argv, help_string);
if(!check_fields("<array element type>", fields, NUM_FIELDS, NULL)){ (3)
field_errors(fields, NUM_FIELDS);
quit(1);
}
if(argc == 0) head = read_sym("<file>"); (4)
if(!read_sym_val("<symbol containing size of array>", NUMBER, (5)
(caddr_t *) &size, &error) ||
fprintf(stderr, "Couldn't read size:\n");
fprintf(stderr, "%s\n", error);
quit(1);
}
<print header> (6)
if(argc == 0){
for(i=0;i<size;i++){
if((ele = array_element(head, i, &error)) == NULL){
fprintf(stderr, "Couldn't get array element\n");
fprintf(stderr, "%s\n", error);
return(False);
}
<print fields in this element> (7)
}
}
}
dbx("print sizeof(array//sizeof(array[0]")
Example 3-4: Extension That Uses Arrays: file.c
#include <stdio.h>
#include <sys/fcntl.h>
#include <kdbx.h>
#include <nlist.h>
#define SHOW_UTT
#include <sys/user.h>
#define KERNEL_FILE
#include <sys/file.h>
#include <sys/proc.h>
static char *help_string =
"file - print out the file table \\\n\
Usage : file [addresses...] \\\n\
If no arguments are present, all file entries with non-zero reference \\\n\
counts are printed. Otherwise, the file entries named by the addresses\\\n\
are printed. \\\n\
";
char buffer[256];
/* *** Implement addresses *** */
FieldRec fields[] = {
{ ".f_type", NUMBER, NULL, NULL },
{ ".f_count", NUMBER, NULL, NULL },
{ ".f_msgcount", NUMBER, NULL, NULL },
{ ".f_cred", NUMBER, NULL, NULL },
{ ".f_data", NUMBER, NULL, NULL },
{ ".f_ops", NUMBER, NULL, NULL },
{ ".f_u.fu_offset", NUMBER, NULL, NULL },
{ ".f_flag", NUMBER, NULL, NULL }
};
FieldRec fields_pid[] = {
{ ".pe_pid", NUMBER, NULL, NULL },
{ ".pe_proc", NUMBER, NULL, NULL },
};
FieldRec utask_fields[] = {
{ ".uu_file_state.uf_lastfile", NUMBER, NULL, NULL }, /* 0 */
{ ".uu_file_state.uf_ofile", ARRAY, NULL, NULL }, /* 1 */
{ ".uu_file_state.uf_pofile", ARRAY, NULL, NULL }, /* 2 */
{ ".uu_file_state.uf_ofile_of", NUMBER, NULL, NULL }, /* 3 */
{ ".uu_file_state.uf_pofile_of", NUMBER, NULL, NULL },/* 4 */
{ ".uu_file_state.uf_of_count", NUMBER, NULL, NULL }, /* 5 */
};
#define NUM_FIELDS (sizeof(fields)/sizeof(fields[0]))
#define NUM_UTASK_FIELDS (sizeof(utask_fields)/sizeof(utask_fields[0]))
static char *get_type(int type)
{
static char buf[5];
switch(type){
case 1: return("file");
case 2: return("sock");
case 3: return("npip");
case 4: return("pipe");
default:
sprintf(buf, "*%3d", type);
return(buf);
}
}
long vn_addr, socket_addr;
int proc_size; /* will be obtained from dbx */
static Boolean prfile(DataStruct ele)
{
char *error, op_buf[12], *ops, buf[256], address[12], cred[12], data[12];
if(!read_field_vals(ele, fields, NUM_FIELDS)){
field_errors(fields, NUM_FIELDS);
return(False);
}
if((long) fields[1].data == 0) return(True);
if((long) (fields[5].data) == 0) ops = " *Null*";
else if((long) (fields[5].data) == vn_addr) ops = " vnops";
else if((long) (fields[5].data) == socket_addr) ops = "sockops";
else format_addr((long) fields[5].data, op_buf);
format_addr((long) struct_addr(ele), address);
format_addr((long) fields[3].data, cred);
format_addr((long) fields[4].data, data);
sprintf(buf, "%s %s %4d %4d %s %11s %11s %6d%s%s%s%s%s%s%s%s%s",
address, get_type((int) fields[0].data), fields[1].data,
fields[2].data, ops, data, cred, fields[6].data,
((long) fields[7].data) & FREAD ? " r" : "",
((long) fields[7].data) & FWRITE ? " w" : "",
((long) fields[7].data) & FAPPEND ? " a" : "",
((long) fields[7].data) & FNDELAY ? " nd" : "",
((long) fields[7].data) & FMARK ? " m" : "",
((long) fields[7].data) & FDEFER ? " d" : "",
((long) fields[7].data) & FASYNC ? " as" : "",
((long) fields[7].data) & FSHLOCK ? " sh" : "",
((long) fields[7].data) & FEXLOCK ? " ex" : "");
print(buf);
return(True);
}
static Boolean prfiles(DataStruct fil, int n)
{
DataStruct ele;
char *error;
if((ele = array_element(fil, n, &error)) == NULL){
fprintf(stderr, "Couldn't get array element\n");
fprintf(stderr, "%s\n", error);
return(False);
}
return(prfile(ele));
}
static void Usage(void){
fprintf(stderr, "Usage : file [addresses...]\n");
quit(1);
}
main(int argc, char **argv)
{
int i;
long nfile, addr;
char *error, *ptr, *resp;
DataStruct fil;
Status status;
check_args(argc, argv, help_string);
argv++;
argc--;
if(!check_fields("struct file", fields, NUM_FIELDS, NULL)){
field_errors(fields, NUM_FIELDS);
quit(1);
}
if(!check_fields("struct pid_entry", fields_pid, 2, NULL)){
field_errors(fields, 2);
quit(1);
}
if(!check_fields("struct utask", utask_fields, NUM_UTASK_FIELDS, NULL)){
field_errors(fields, NUM_UTASK_FIELDS);
quit(1);
}
if(!read_sym_addr("vnops", &vn_addr, &error) ||
!read_sym_addr("socketops", &socket_addr, &error)){
fprintf(stderr, "Couldn't read vnops or socketops:\n");
fprintf(stderr, "%s\n", error);
quit(1);
}
print("Addr Type Ref Msg Fileops F_data Cred Offset
Flags");
print("=========== ==== === === ======= =========== =========== ======
=====");
if(argc == 0){
/*
* New code added to access open files in processes, in
* the absence of static file table, file, nfile, etc..
*/
/*
* get the size of proc structure
*/
sprintf(buffer, "set $hexints=0");
dbx(buffer, False);
sprintf(buffer, "print sizeof(struct proc)");
dbx(buffer, True);
if((resp = read_response(&status)) == NULL){
print_status("Couldn't read sizeof proc", &status);
proc_size = sizeof(struct proc);
}
else
proc_size = strtoul(resp, (char**)NULL, 10);
free(resp);
if ( get_all_open_files_from_active_processes() ) {
fprintf(stderr, "Couldn't get open files from processes:\n");
quit(1);
}
}
else {
while(*argv){
addr = strtoul(*argv, &ptr, 16);
if(*ptr != '\0'){
fprintf(stderr, "Couldn't parse %s to a number\n", *argv);
quit(1);
}
if(!cast(addr, "struct file", &fil, &error)){
fprintf(stderr, "Couldn't cast address to a file:\n");
fprintf(stderr, "%s\n", error);
quit(1);
}
if(!prfile(fil))
fprintf(stderr, "Continuing with next file address.\n");
argv++;
}
}
quit(0);
}
/*
* Figure out the location of the utask structure in the supertask
* #define proc_to_utask(p) (long)(p+sizeof(struct proc))
*/
/*
* Figure out if this a system with the capability of
* extending the number of open files per process above 64
*/
#ifdef NOFILE_IN_U
# define OFILE_EXTEND
#else
# define NOFILE_IN_U NOFILE
#endif
/*
* Define a generic NULL pointer
*/
#define NIL_PTR(type) (type *) 0x0
get_all_open_files_from_active_processes()
{
long pidtab_base; /* Start address of the process table */
long npid; /* Number of processes in the process table */
char *error;
if (!read_sym_val("pidtab", NUMBER, &pidtab_base, &error) ||
!read_sym_val("npid", NUMBER, &npid, &error) ){
fprintf(stderr, "Couldn't read pid or npid:\n");
fprintf(stderr, "%s\n", error);
quit(1);
}
if ( check_procs (pidtab_base, npid) )
return(0);
else
return(1);
}
check_procs(pidtab_base, npid)
long pidtab_base;
long npid;
{
int i, index, first_file;
long addr;
DataStruct pid_entry_struct, pid_entry_ele, utask_struct, fil;
DataStruct ofile, pofile;
char *error;
long addr_of_proc, start_addr, val1, fp, last_fp;
char buf[256];
/*
* Walk the pid table
*/
pid_entry_struct = read_sym("pidtab");
for (index = 0; index < npid; index++)
{
if((pid_entry_ele = array_element(pid_entry_struct, index, &error))==NULL){
fprintf(stderr, "Couldn't get pid array element %d\n", index);
fprintf(stderr, "%s\n", error);
continue;
}
if(!read_field_vals(pid_entry_ele, fields_pid, 2)) {
fprintf(stderr, "Couldn't get values of pid array element %d\n", index);
field_errors(fields_pid, 2);
continue;
}
addr_of_proc = (long)fields_pid[1].data;
if (addr_of_proc == 0)
continue;
first_file = True;
addr = addr_of_proc + proc_size;
if(!cast(addr, "struct utask", &utask_struct, &error)){
fprintf(stderr, "Couldn't cast address to a utask (bogus?):\n");
fprintf(stderr, "%s\n", error);
continue;
}
if(!read_field_vals(utask_struct, utask_fields, 3)) {
fprintf(stderr, "Couldn't read values of utask:\n");
field_errors(fields_pid, 3);
continue;
}
addr = (long) utask_fields[1].data;
if (addr == NULL)
continue;
for(i=0;i<=(int)utask_fields[0].data;i++){
if(i>=NOFILE_IN_U){
if (utask_fields[3].data == NULL)
continue;
start_addr = (long)((long *)utask_fields[3].data + i-NOFILE_IN_U) ;
if(!read_memory(start_addr , sizeof(struct file *), (char *)&val1,
&error)) {
fprintf(stderr,"Start addr:0x%lx bytes:%d\n", start_addr, sizeof(long
*));
fprintf(stderr, "Couldn't read memory for extn files: %s\n", error);
continue;
}
}
else {
ofile = (DataStruct) utask_fields[1].data;
pofile = (DataStruct) utask_fields[2].data;
}
if (i < NOFILE_IN_U)
if(!array_element_val(ofile, i, &val1, &error)){
fprintf(stderr,"Couldn't read %d'th element of ofile|pofile:\n", i);
fprintf(stderr, "%s\n", error);
continue;
}
fp = val1;
if(fp == 0) continue;
if(fp == last_fp) continue; /* eliminate duplicates */
last_fp = fp;
if(!cast(fp, "struct file", &fil, &error)){
fprintf(stderr, "Couldn't cast address to a file:\n");
fprintf(stderr, "%s\n", error);
quit(1);
}
if (first_file) {
sprintf(buf, "[Process ID: %d]", fields_pid[0].data);
print(buf);
first_file = False;
}
if(!prfile(fil))
fprintf(stderr, "Continuing with next file address.\n");
}
} /* for loop */
return(True);
} /* end */
Example 3-5: Extension That Uses Global Symbols: sum.c
#include <stdio.h>
#include <kdbx.h>
static char *help_string =
"sum - print a summary of the system \\\n\
Usage : sum \\\n\
";
static void read_var(name, type, val)
char *name;
int type;
long *val;
{
char *error;
long n;
if(!read_sym_val(name, type, &n, &error)){
fprintf(stderr, "Reading %s:\n", name);
fprintf(stderr, "%s\n", error);
quit(1);
}
*val = n;
}
main(argc, argv)
int argc;
char **argv;
{
DataStruct utsname, cpup, time;
char buf[256], *error, *resp, *sysname, *release, *version, *machine;
long avail, secs, tmp;
check_args(argc, argv, help_string);
read_var("utsname.nodename", STRING, &resp);
sprintf(buf, "Hostname : %s", resp);
print(buf);
free(resp);
read_var("ncpus", NUMBER, &avail);
/*
* cpup no longer exists, emmulate platform_string(),
* a.k.a. get_system_type_string().
read_var("cpup.system_string", STRING, &resp);
*/
read_var("rpb->rpb_vers", NUMBER, &tmp);
if (tmp < 5)
resp = "Unknown System Type";
else
read_var(
"(char *)rpb + rpb->rpb_dsr_off + "
"((struct rpb_dsr *)"
" ((char *)rpb + rpb->rpb_dsr_off))->rpb_sysname_off + sizeof(long)",
STRING, &resp);
sprintf(buf, "cpu: %s\tavail: %d", resp, avail);
print(buf);
free(resp);
read_var("boottime.tv_sec", NUMBER, &secs);
sprintf(buf, "Boot-time:\t%s", ctime(&secs));
buf[strlen(buf) - 1] = '\0';
print(buf);
read_var("time.tv_sec", NUMBER, &secs);
sprintf(buf, "Time:\t%s", ctime(&secs));
buf[strlen(buf) - 1] = '\0';
print(buf);
read_var("utsname.sysname", STRING, &sysname);
read_var("utsname.release", STRING, &release);
read_var("utsname.version", STRING, &version);
read_var("utsname.machine", STRING, &machine);
sprintf(buf, "Kernel : %s release %s version %s (%s)", sysname, release,
version, machine);
print(buf);
quit(0);
}
3.4 Compiling Custom Extensions
After you
have written the extension, you need to compile it. To compile the extension,
enter the following command:
% cc -o test test.c -lkdbx
# kdbx -k /vmunix
dbx version 3.12.1
Type 'help' for help.
(kdbx) test
Hostname : system.dec.com
cpu: DEC3000 - M500 avail: 1
Boot-time: Fri Nov 6 16:09:10 1992
Time: Mon Nov 9 10:51:48 1992
Kernel : OSF1 release 1.2 version 1.2 (alpha)
(kdbx)
3.5 Debugging Custom Extensions
The kdbx debugger and the dbx debugger include the capability
to communicate with each other using two named pipes. The task of debugging
an extension is easier if you use a workstation with two windows or two terminals.
In this way, you can dedicate one window or terminal to the kdbx
debugger and one window or terminal to the dbx debugger. However,
you can debug an extension from a single terminal. This section explains how
to begin your kdbx and dbx sessions when you have two
windows or terminals and when you have a single terminal. The examples illustrate
debugging the test extension that was compiled in Section 3.4.
# kdbx -k /vmunix
dbx version 3.12.1
Type 'help' for help.
stopped at [thread_block:1440 ,0xfffffc00002de5b0] Source not available
# dbx test
dbx version 3.12.1
Type 'help' for help.
(dbx)
(kdbx) procpd
(dbx) run < /tmp/pipeout > /tmp/pipein
# echo 'procpd' | kdbx -k /vmunix &
dbx version 3.12.1
Type 'help' for help.
stopped at [thread_block:1403 ,0xfffffc000032d860] Source not available
#
# dbx test
dbx version 3.12.1
Type 'help' for help.
(dbx)