To assist in
debugging kernel code, you can write an extension to the
kdbx
debugger.
Extensions interact with
kdbx
and enable you
to examine kernel data relevant to debugging the source program.
This chapter
provides the following:
A list of considerations before you begin writing extensions (Section 3.1)
A description of the
kdbx
library routines
that you can use to write extensions (Section 3.2)
Examples of
kdbx
extensions (Section 3.3)
Instructions for compiling extensions (Section 3.4)
Information to help you debug your
kdbx
extensions (Section 3.5)
The Tru64 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.
3.1 Basic Considerations for Writing Extensions
Before writing an extension, consider the following:
The information that is needed
Identify the kernel variables and symbols that you need to examine.
The means for displaying the information
Display the information so that anyone who needs to use it can read and understand it.
The need to provide useful error checking
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.
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.
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.
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:
The
StatusType
data type is used to declare the status type and can
take on any one of the following values:
OK
, which indicates that no error occurred
Comm
, which indicates a communication error
Local
, which indicates other types of errors
The following is the type definition for the
StatusType
data type:
typedef enum { OK, Comm, Local } StatusType;
The
Status
data type is returned by some library routines to inform
the caller of the status of the call.
Library routines using this data type
fill in the
type
field with the call status from
StatusType
.
Upon return, callers check the
type
field, and if it is not set to
OK
, they can pass the
Status
structure to the
print_status
routine
to generate a detailed error message.
The following is the type definition for the
Status
data type:
typedef struct { StatusType type; union { int comm; int local; } u; } Status;
The values in
comm
and
local
provide
the error code interpreted by
print_status
.
The
FieldRec
data type, which is used to declare a field of interest
in a data structure.
The following is the type definition for the
FieldRec
data type:
typedef struct { char *name; int type; caddr_t data; char *error; } FieldRec;
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
DataStruct
, data type, which is used to declare data structures
with opaque data types.
The following is the type definition for the
DataStruct
data type:
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.
This function has the following syntax:
char * addr_to_proc(
long addr
);
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);
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:
DataStruct array_element(
DataStruct sym,
int i,
char** error
);
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.
Note
The
read_sym
,array_element
, andread_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 theread_sym
function in Section 3.2.27.)
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); }
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.
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
);
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); }
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:
unsigned int array_size(
DataStruct sym,
char** error
);
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); }
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:
Boolean cast(
long addr,
char* type,
DataStruct* ret_type,
char** error
);
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); }
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.
This function has the following syntax:
void check_args(
int argc,
char** argv,
char* help_string
);
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 |
You should include the
check_args
function early
in your extension to be sure that arguments are correct.
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); }
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.
This function has the following syntax:
Boolean check_fields(
char* symbol,
FieldRec* fields,
int nfields,
char** hints
);
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); }
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.
This function has the following syntax:
void context(
Boolean user
);
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++){
.
.
.
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.
void dbx(
char* command,
Boolean expect_output
);
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); }
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.
This function has the following syntax:
DataStruct deref_pointer(
DataStruct data
);
Argument | Input/Output | Description |
data | Input | Names the data structure that is being dereferenced |
For example:
structure = deref_pointer(struct_pointer);
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:
void field_errors(
FieldRec* fields,
int nfields
);
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); }
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:
extern char* format_addr(
long addr,
char* buffer
);
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); }
The
free_sym
function releases the memory held by a specified symbol.
This function has the following syntax:
void free_sym(
DataStruct sym
);
Argument | Input/Output | Description |
sym | Input | Names the symbol that is using memory that can be freed |
For example:
free_sym(rec->data);
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.
This function has the following syntax:
void krash(
char* command,
Boolean quote,
Boolean expect_output
);
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:
The
command
argument contains the
dbx
command to be executed, such as
p
for
print
.
The
buf
argument contains the full
dbx
command line; for example,
buf
might contain:
p ((struct mount *) 0xffffffff8196db30).m_next
The
type
argument contains the data type
of each node in the list, as in
struct mount *
.
The
addr
argument contains the address
of the current node in the list; for example, the current node might be at
address
0xffffffff8196db30
.
The
last
argument contains the address
of the previous node in the list.
In this case,
last
contains
zero (0).
The
i
argument is the current node's index.
In this case,
i
contains 1.
The
next
argument is the address of the
next node in the list; for example, the next node might be at address
0xffffffff8196d050
.
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:
Boolean list_nth_cell(
long addr,
char* type,
int n,
char* next_field,
Boolean do_check,
long* val_ret,
char** error
);
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); }
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 an extension that is
included with the operating system or an extension that you create.
This function has the following syntax:
void new_proc(
char* args,
char** output_ret
);
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); }
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.
This function has the following syntax:
Boolean next_number(
char* buf,
char** next,
long* ret
);
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;
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:
char* next_token(
char* ptr,
int* len_ret,
char** next_ret
);
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 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.
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
);
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"); }
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:
void print_status(
char* message,
Status* status
);
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); }
3.2.22 Exiting from an Extension
The
quit
function sends a
quit
command to
kdbx
.
This function has the following format:
void quit(
int i
);
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); }
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.
This function has the following format:
Boolean read_field_vals(
DataStruct data,
FieldRec* fields,
int nfields
);
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); }
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.
This function has the following format:
char* read_line(
Status* status
);
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); }
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.
This function has the following format:
Boolean read_memory(
long start_addr,
int n,
char* buf,
char** 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); }
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.
This function has the following syntax:
char* read_response(
Status* status
);
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); }
3.2.27 Reading Symbol Representations
The
read_sym
function returns a representation of the named symbol.
This function has the following format:
DataStruct read_sym(
char* name
);
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 |
Often you use the result returned by the
read_sym
function as the input argument of the
array_element
,
array_element_val
, or
read_field_vals
function.
For example:
busses = read_sym("bus_list");
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:
Boolean read_sym_addr(
char* name,
long* ret_val,
char** error
);
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); }
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:
Boolean read_sym_val(
char* name,
int type,
long* ret_val,
char** error
);
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); }
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:
char* struct_addr(
DataStruct data
);
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(); }
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.
This function has the following format:
Boolean to_number(
char* str,
long* val
);
Argument | Input/Output | Description |
str | Input | Contains the string to be converted |
val | Output | Contains the numerical equivalent of the string |
This function returns TRUE if successful, FALSE if conversion was not possible.
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(); } }
3.3 Examples of kdbx Extensions
This section contains examples of the three types of extensions provided
by the
kdbx
debugger:
Extensions that use lists.
Example 3-1
provides a C language template and
Example 3-2
is the source
code for the
/var/kdbx/callout
extension, which shows how
to use linked lists in developing an extension.
Extensions that use arrays.
Example 3-3
provides a C language template and
Example 3-4
is the source
code for the
/var/kdbx/file
extension, which shows how
to develop an extension using arrays.
Extensions that use global symbols.
Example 3-5
is the source code for the
/var/kdbx/sum
extensions, which
shows how to pull global symbols from the kernel.
A template is not provided
because the means for pulling global symbols from a kernel can vary greatly,
depending upon the desired output.
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); }
The help string is output by the
check_args
function if the user enters the
help extension_name
command
at the
kdbx
prompt.
The first line of the help string should
be a one-line description of the extension.
The rest should be a complete
description of the arguments.
Also, each line should end with the string
\\\n\
.
[Return to example]
Every structure field to be extracted needs an entry. The first field is the name of the next extracted field; the second field is the type. The last two fields are for output and initialize to NULL. [Return to example]
Specifies the type of the list that is being traversed. [Return to example]
Specifies the variable that holds the head of the list. [Return to example]
Specifies the table header string. [Return to example]
Specifies the type of the list that is being traversed. [Return to example]
Specifies the structure type. [Return to example]
Extracts, formats, and prints the field information. [Return to example]
#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] } } }
The help string is output by the
check_args
function if the user enters the
help extension_name
command
at the
kdbx
prompt.
The first line of the help string should
be a one-line description of the extension.
The rest should be a complete
description of the arguments.
Also, each line should end with the string
\\\n\
.
[Return to example]
Every structure field to be extracted needs an entry. The first field is the name of the next extracted field; the second field is the type. The last two fields are for output and initialize to NULL. [Return to example]
Specifies the type of the element in the array. [Return to example]
Specifies the variable containing the beginning address of the array. [Return to example]
Specifies the variable containing the size of the array. Note that reading variables is only one way to access this information. Other methods include the following:
Defining the array size with a
#define
macro call.
If you use this method, you need to include the appropriate header
file and use the macro in the extension.
Querying
dbx
for the array size as follows:
dbx("print sizeof(array//sizeof(array[0]")
Hard coding the array size.
Specifies the string to be displayed as the table header. [Return to example]
Extracts, formats, and prints the field information. [Return to example]
#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
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.
Extensions provided with the operating system are
located in the
/var/kdbx
directory.
The following example shows how to invoke the
test
extension from within the
kdbx
debugger:
#
kdbx -k /vmunix
dbx version 5.0 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.
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:
Open two sessions: one running
kdbx
on
the running kernel and the other running
dbx
on the source
file for the custom extension
test
as follows:
Begin the kdbx session:
#
kdbx -k /vmunix
dbx version 5.0 Type 'help' for help. stopped at [thread_block:1440 ,0xfffffc00002de5b0] Source not available
Begin the dbx session:
#
dbx test
dbx version 5.0 Type 'help' for help. (dbx)
Set up
kdbx
and
dbx
to communicate with each other.
In the
kdbx
session, enter
the
procpd
alias to create the files
/tmp/pipein
and
/tmp/pipeout
as follows:
(kdbx)
procpd
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.
In the
dbx
session, enter the
run
command to execute the
test
extension in
the
kdbx
session, specifying the files
/tmp/pipein
and
/tmp/pipeout
on the command line as follows:
(dbx)
run < /tmp/pipeout > /tmp/pipein
As you step through the extension in the
dbx
session, you see the results of any action in the
kdbx
session.
At this point, you can use the available
dbx
commands
and options.
If you are using one terminal, perform the following steps to set up
your
kdbx
and
dbx
sessions:
Issue the following command to invoke
kdbx
with the debugging environment:
#
echo 'procpd' | kdbx -k /vmunix &
dbx version 5.0 Type 'help' for help. stopped at [thread_block:1403 ,0xfffffc000032d860] Source not available #
Invoke the
dbx
debugger as follows:
#
dbx test
dbx version 5.0 Type 'help' for help. (dbx)
As you step through the extension in the
dbx
session, you see the results of any action in the
kdbx
session.
At this point, you can use the available
dbx
commands
and options.