1<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 2 "http://www.w3.org/TR/REC-html40/loose.dtd"> 3<html> 4 5<head> 6<title>Source Level Debugging in Poly/ML</title> 7</head> 8 9<body bgcolor="#FFFFFF"> 10 11<h2>Source Level Debugging in Poly/ML</h2> 12 13<p>Poly/ML includes a source-level debugger. You can use it to set breakpoints 14 in the program and print the values of local variables. To turn on debugging 15 for a particular piece of code set the compiler variable <tt>PolyML.Compiler.debug</tt> 16 to true. You can freely mix functions compiled with debugging on with 17 functions compiled with debugging off, you simply can't set a breakpoint in 18 a non-debuggable function or print its internal state. The debugging 19 control functions are all in the <tt>PolyML.Debug</tt> structure<tt>. </tt>It 20 is often convenient to open this structure before debugging a program.</p> 21 22<h3><big>An Example Session.</big></h3> 23 24<p>To see how debugging works we'll follow through an example session. We have a 25small function that is supposed to add together a list of pairs of integers but it has an 26error in it which causes it not to terminate. We turn on debugging and compile in 27the program.</p> 28 29<p><tt>> <strong>PolyML.Compiler.debug := true;</strong><br> 30val it = () : unit<br> 31> <strong>fun addList a l =<br> 32 let<br> 33 fun f (b,c) = a+b+c<br> 34 in<br> 35 case l of<br> 36 [] => a<br> 37 | (x::y) =><br> 38 39let<br> 40 41val v = f x<br> 42 43val l' = y<br> 44 45in<br> 46 47addList v l<br> 48 49end<br> 50 end;</strong><br> 51<br> 52val addList = fn : int -> (int * int) list -> int<br> 53> <strong>open PolyML.Debug;</strong></tt></p> 54 55<p>We set a breakpoint in the function<tt> f</tt> using the <tt>breakIn</tt> function and 56apply the function to some arguments.</p> 57 58<p><tt>> <strong>breakIn "f";</strong><br> 59val it = () : unit<br> 60> <strong>addList 0 [(1,2), (3, 4)];</strong></tt></p> 61 62<p>The function prints the line number and stops at the breakpoint.</p> 63 64<p><tt>line:3 function:addList()f<br> 65debug> </tt></p> 66 67<p>The name <tt>addList()f</tt> is the full name of the function and we could have used 68this in place of <tt>f</tt> when setting the breakpoint. The <tt>"debug>"</tt> 69prompt is almost identical to the normal Poly/ML <tt>">"</tt> prompt. 70The only difference is that variables which are in scope at the breakpoint are available 71as though they were declared globally. So we can print the values of <tt>a</tt>, <tt>b</tt>, 72<tt>c</tt> and <tt>l</tt>.</p> 73 74<p><tt>debug> <strong>a;</strong><br> 75val it = 0 : int<br> 76debug> <strong>b;</strong><br> 77val it = 1 : int<br> 78debug> <strong>c;</strong><br> 79val it = 2 : int<br> 80debug> <strong>l;</strong><br> 81val it = [(1, 2), (3, 4)] : (int * int) list<br> 82debug> </tt></p> 83 84<p>In addition anything in the original top level is also available, provided it is not 85masked by a local declaration, so we can continue the program by calling the <tt>continue</tt> 86function which we opened from <tt>PolyML.Debug</tt>.</p> 87 88<p><tt>debug> <strong>continue();</strong><br> 89val it = () : unit<br> 90line:3 function:addList()f<br> 91debug></tt></p> 92 93<p>This continues and we find ourselves back in <tt>f</tt> at the breakpoint again. 94We expect that and check the value of <tt>a</tt>.</p> 95 96<p><tt>debug> <strong>a;</strong><br> 97val it = 3 : int<br> 98debug></tt></p> 99 100<p>However when we check <tt>b</tt> something seems to be wrong and printing <tt>l</tt> 101confirms it.</p> 102 103<p><tt>debug> <strong>b;</strong><br> 104val it = 1 : int<br> 105debug> <strong>l;</strong><br> 106val it = [(1, 2), (3, 4)] : (int * int) list<br> 107debug></tt></p> 108 109<p>We don't seem to be making any progress. We go up the stack to the recursive call 110of <tt>addList</tt> in order to check that the value of <tt>l'</tt> is correct. We 111have to go up twice because <tt>l'</tt> is not local to <tt>f</tt> nor is it available at 112the point where <tt>f</tt> was called. It is only available at the point where <tt>addList</tt> 113was called recursively.</p> 114 115<p><tt>debug> <strong>up();</strong><br> 116line:9 function:addList<br> 117val it = () : unit<br> 118debug> <strong>up();</strong><br> 119line:12 function:addList<br> 120val it = () : unit<br> 121debug><strong> l';</strong><br> 122val it = [(3, 4)] : (int * int) list<br> 123debug> </tt></p> 124 125<p>This looks fine so the problem was not that <tt>l</tt>' had the wrong value. We 126print the values of everything using the <tt>dump</tt> function to see if that helps.</p> 127 128<p><tt>debug> <strong>dump();</strong><br> 129Function addList()f: c = 2 b = 1 l = [(1, 2), (3, 4)] a = 3 <br> 130Function addList: x = (1, 2) y = [(3, 4)] f = fn l = [(1, 2), (3, 4)] a = 3 <br> 131Function addList: l' = [(3, 4)] v = 3 x = (1, 2) y = [(3, 4)] f = fn<br> 132l = [(1, 2), (3, 4)] a = 0 <br> 133<br> 134val it = () : unit</tt></p> 135 136<p>At this stage it is clear that we are passing the original value of <tt>l</tt> rather 137than what we intended,<tt> l'</tt>. We abort the function by typing control-C and f.</p> 138 139<p><tt>debug> <strong>^C</strong><br> 140=><strong>f</strong><br> 141Compilation interrupted<br> 142Pass exception to function being debugged (y/n)?<strong>y</strong><br> 143Exception- Interrupt raised<br> 144> </tt></p> 145 146<p>This returns us to the top level. We now fix the error and clear the breakpoint. </p> 147 148<p><tt>> <strong>fun addList a l =<br> 149 let<br> 150 fun f (b,c) = a+b+c<br> 151 in<br> 152 case l of<br> 153 [] => a<br> 154 | (x::y) =><br> 155 156let<br> 157 158val v = f x<br> 159 160val l' = y<br> 161 162in<br> 163 164addList v l'<br> 165 166end<br> 167 end;</strong><br> 168val addList = fn : int -> (int * int) list -> int<br> 169> <strong>clearIn "f";</strong><br> 170val it = () : unit</tt></p> 171 172<p>As a final check we turn on tracing to check that the values are as we expect and run 173the program with the same input as before.</p> 174 175<p><tt>> <strong>trace true;</strong><br> 176val it = () : unit<br> 177> <strong>addList 0 [(1,2), (3,4)];</strong><br> 178addList entered l = [(1, 2), (3, 4)] a = 0 <br> 179 addList()f entered c = 2 b = 1 l = [(1, 2), (3, 4)] a = 0 <br> 180 addList()f returned 3<br> 181 addList entered l = [(3, 4)] a = 3 <br> 182 addList()f entered c = 4 b = 3 l = [(3, 4)] a = 3 <br> 183 addList()f returned 10<br> 184 addList entered l = [] a = 10 <br> 185 addList returned 10<br> 186 addList returned 10<br> 187addList returned 10<br> 188val it = 10 : int<br> 189> </tt></p> 190 191<p>This seems to have worked fine so we can now turn off <tt>PolyML.Compiler.debug</tt> 192and compile the function without debugging.</p> 193 194<p> </p> 195 196<h3><big>Reference</big></h3> 197 198<h3>Tracing program execution</h3> 199 200<p><tt> val trace = fn : bool -> unit<br> 201 </tt>The <tt>trace</tt> function turns on and off function tracing. Function 202 tracing prints the arguments and results of every debuggable function.</p> 203 204<h3>Breakpoints</h3> 205 206<p><tt> val breakAt = fn : string * int -> unit<br> 207 val breakIn = fn : string -> unit<br> 208 val breakEx = fn : exn -> unit<br> 209 val clearAt = fn : string * int -> unit<br> 210 val clearIn = fn : string -> unit<br/> 211 val clearEx = fn : exn -> unit</tt></p> 212 213<p>Breakpoints can be set with the <tt>breakAt</tt>, <tt>breakIn</tt> or <tt>breakEx</tt> 214 functions and cleared with <tt>clearAt</tt>, clearIn or <tt>clearEx</tt>. 215 <tt>breakAt</tt> takes a file name and line number and <tt>breakIn</tt> a function 216 name. The file name and line have to given exactly otherwise the breakpoint 217 will not work. <tt>breakIn</tt> is more flexible about the function name. 218 It can be the function name used in the declaration (e.g. <tt>f</tt>) or the 219 full "path name". The latter is useful when the program contains 220 several functions with the same name since setting a breakpoint in <tt>f</tt> 221 stops in any function called <tt>f</tt>. <tt>breakIn</tt> and <tt>breakAt</tt> 222 simply record that you want a breakpoint. There is no check when they 223 are called that the appropriate location actually exists and you can set a breakpoint 224 for a function and then compile it later. <tt>breakEx</tt> sets a breakpoint 225 for a particular exception and causes the program to stop at the end of the 226 function that raises the exception unless it is also handled there. The argument 227 is the exception to trap. It is possible to trap exceptions that take a parameter. 228 Just provide it with a dummy parameter to create a value of type <tt>exn</tt> 229 that can be passed to <tt>breakEx</tt>. The actual parameter value will be ignored 230 and the debugger will be entered whenever the exception is raised with any parameter 231 value. </p> 232 233<p>When a breakpoint is reached the program stops with a <tt>debug></tt> prompt. 234This is a normal Poly/ML top-level and you can type any ML expression. The only 235difference is that local variables in the function being debugged are available as though 236they had been declared at the top-level. You can print them or use them in any way 237that you could with a normal variable. This includes local functions which can be 238applied to local values, constants or globals. You cannot change the value of a variable 239unless it is a reference. At a breakpoint you can continue or single-step the 240program or you can raise an exception. This is usually the most convenient way of 241aborting a program and getting back to the normal top-level unless the program has a 242handler for the exception you raise.</p> 243 244<h3>Single Stepping and Continuing</h3> 245 246<p><tt>val continue = fn : unit -> unit<br> 247val step = fn : unit -> unit<br> 248val stepOver = fn : unit -> unit<br> 249val stepOut = fn : unit -> unit</tt></p> 250 251<p><tt>continue</tt> runs the program to the next breakpoint.<tt> step</tt>, <tt>stepOver</tt> 252and <tt>stepOut</tt> are different ways of single-stepping through a function. 253 <tt>step</tt> runs the program up to the next breakable point which is often 254the next source line. If evaluation involves calling a function then it may stop at 255the beginning of the function. By contrast <tt>stepOver</tt> stops at the next line 256within the current function only. <tt>stepOut</tt> goes further and stops only 257within the function which called the current function. If a called function includes 258a breakpoint they always stop there.</p> 259 260<h3>Examining and Traversing the Stack</h3> 261 262<p><tt>val up = fn : unit -> unit<br> 263val down = fn : unit -> unit<br> 264val dump = fn : unit -> unit<br> 265val variables = fn : unit -> unit</tt></p> 266 267<p><tt>up</tt> and <tt>down</tt> move the focus between called and calling functions 268allowing you to view local variables at different levels. This is particularly 269useful for recursive functions. <tt>The variables</tt> function prints all the 270variables in scope at the current point. <tt>dump</tt> gives a complete stack trace.</p> 271 272<h3>Notes</h3> 273 274<p>The current implementation includes most values but not types or structures. 275 A recursive function is not available within the function itself.</p> 276 277<p>The compiler adds debugging information which considerably increases the run time of 278the program. It is probably best to turn debugging on only when it is needed.</p> 279 280<p>The example shows debugging when all the variables have monomorphic types. The 281debugger does not have access to any run-time type information so it cannot always know 282how to print a value inside a polymorphic type. For example</p> 283 284<p><tt>> fun map f [] = [] | map f (a::b) = f a :: map f b;<br> 285val map = fn : ('a -> 'b) -> 'a list -> 'b list<br> 286> breakIn "map";<br> 287val it = () : unit<br> 288> map (fn i => i+1) [1,2,3];<br> 289line:1 function:map<br> 290debug> a;<br> 291val it = ? : 'a</tt></p> 292 293<p>If you know the type is int you can add a type constraint.</p> 294 295<p><tt>debug> a:int;<br> 296val it = 1 : int<br> 297debug> </tt></p> 298 299<p>It is though equally possible to use the wrong constraint and crash Poly/ML. 300 Future versions of Poly/ML may treat polymorphic variables as opaque 301 which would prevent this but also prevent "safe" coercions.</p> 302<p> </p> 303 304</body> 305</html> 306