How can I do debugging in PostScript? I can use GhostView/GhostScript on Linux, but I can't view the stack, dictionaries, etc.
I just discovered a really useful hack.
<<
/break{ /hook /pause load store }
/cont{ /hook {} store }
/doprompt{
(\nbreak>)print
flush(%lineedit)(r)file
cvx {exec}stopped pop }
/pause{ doprompt }
/hook{}
>> begin
It's an 8-line EMBEDDABLE debugger. I cooked this up for my 8086 emulator. Put hook
in the main loop, put break
in a procedure to trigger a pause at the next hook (I put it in suspicious OPCODE procs, and hook is where the break takes its first effect). Hook calls pause and pause calls doprompt
which gives you a one-line "break>" prompt. Typing cont
here will clear the hook and keep spinning, not executing any pauses until another break
is encountered. You can also inspect values and execute code at the prompt, but n.b. execution resumes when you hit enter, so if you need additional lines, call doprompt
or pause
at the end of the line. Errors are ignored while executing the command (you don't want the debugger to crash the program, that'd be stupid!). I suppose I could combine pause and doprompt and eliminate a name; but the goal here is not machine efficiency but a clear collection of concepts: this code, to be at all useful in debugging other code, needs to be easy to scan and verify.
How is this a debugger, it just reads a line?!
Remember you've got =
and ==
to investigate values. forall
and get
to bust up arrays and stuff. To really find out where you are, do countexecstack array execstack ==
for a readable dump of the whole caboodle. That is, a backtrace of the current position in execution stack which contains the tails of all partially-executed procedures and files waiting to be resumed when the current frame returns.
There's quite a lot of debugging that can be done without a debugger per se merely by instrumenting your program (adding printf
s, so to speak).
Just now I ran into an error that my debugger couldn't help me with because the debugger itself was crashing on some overly-clever stuff like
/E [ 0 0 10 ] def %eye point
/crackE { % set pointers into E
/ex E 0 1 getinterval cvx def
/ey E 1 1 getinterval cvx def
/ez E 2 1 getinterval cvx def
} def crackE
So the actual error I was investigating was
GPL Ghostscript 8.62 (2008-02-29)
Copyright (C) 2008 Artifex Software, Inc. All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Error: /stackunderflow in --forall--
Operand stack:
--nostringval--
Execution stack:
%interp_exit .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- --nostringval-- --nostringval-- false 1 %stopped_push 1905 1 3 %oparray_pop 1904 1 3 %oparray_pop 1888 1 3 %oparray_pop 1771 1 3 %oparray_pop --nostringval-- %errorexec_pop .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- --nostringval-- --nostringval-- 0.238095 0.047619 0.952381 --nostringval-- %for_real_continue 68.5714 17.1429 360.048 --nostringval-- %for_real_continue --nostringval--
Dictionary stack:
--dict:1151/1684(ro)(G)-- --dict:0/20(G)-- --dict:121/200(L)-- --dict:13/20(L)-- --dict:1/2(L)--
Current allocation mode is local
Last OS error: 2
Current file position is 3241
GPL Ghostscript 8.62: Unrecoverable error, exit code 1
And all I really need to know is what that --nostringval--
thing on the operand stack really is.
So I put this at the start of the program
/forall { pstack()= forall } bind def
and run it again and now
{MO matmul 0 --get-- --aload-- --pop-- proj action} Error: /stackunderflow in --forall-- Operand stack: --nostringval-- ...
Just before the error is the final stackdump (using ==
) which tells me that I have a procedure body missing its dataset.
pstack
is a bit blunt compared to something like this
/args { dup 1 add copy -1 1 { -1 roll ==only ( ) print } for } def
/forall { 2 args (forall)= forall } bind def
which would be more useful for tracking down erroneous data in apparently working code. This is also the way very early versions of Distiller produced optimized .ps files by defining only the drawing operations to dump themselves, the rest of the calculation is "distilled" out.
Some tricks
()= %print a newline
=string %built-in 128-byte buffer used by = and ==
/object =string cvs print %convert object to string and print without newline
/stack { count dup 1 add copy { = } repeat pop } def % this is the code for the stack operator
66 (#) dup 0 3 index put print %m non-destructively print a "char"
[I wrote '=' instead of 'stack' here earlier. A bad blunder. Edit: added missing pop
to /stack
.]
Another way to investigate an error is to change the error handler. To investigate the /stackunderflow
error described above I could have used
errordict/stackunderflow{dup == /stackunderflow signalerror}put
instead of specializing forall
. To learn about this rather arcane aspect of postscript, read up on errordict
stop
and stopped
. And interactively, take a peek at errordict{exch =only ==}forall
. signalerror
in ghostscript is called .error
in Adobe interpreters. Its job is to take the snapshots of the stacks, and then call stop
to pop the exec stack. So the dup ==
here and the pstack
above are essentially the same 'moment' of the error, before the stop
. Your interactive session (and the preceding program in gs's normal mode) are bracketed deeper on the exec stack with the equivalent of //your-program stopped { handleerror } if
. It is handleerror
which uses the snapshots (after the program has been otherwise purged) to print the error report with its uninformative stack printouts.
There are replacements you can find for handleerror
which you can (ehandle.ps)run
at the beginning of the erring program to produce different styled error reports.
I just discovered this while re-reading my examples here. You can also investigate the stacks after the error, if the interpreter is still giving you a prompt. Error information is saved in the $error
dictionary, including the snapshots of the stacks.
GS>[ 1 2 3 ] [4 5 6] bogus
Error: /undefined in bogus
Operand stack:
--nostringval-- --nostringval--
Execution stack:
%interp_exit .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- --nostringval-- %loop_continue --nostringval-- --nostringval-- false 1 %stopped_push .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval--
Dictionary stack:
--dict:1168/1684(ro)(G)-- --dict:0/20(G)-- --dict:77/200(L)--
Current allocation mode is local
Current file position is 24
GS<2>
GS<2>$error{pop ==}forall
/dstack
/recordstacks
/globalmode
/newerror
/.nosetlocal
/estack
/errorinfo
/.inerror
/SubstituteFont
/position
/binary
/ostack
/command
/errorname
GS<2>$error/ostack get ==
[[1 2 3] [4 5 6]]
GS<2>
Of course, here the objects were still on the stack. But $error
is there to peek at. Don't try this: $error ===
. TMI.
One very useful bit of information you can get from $error
is a pretty-print of /estack
, the copy of the execution stack at the point of error.
PS<3>$error /estack get ==
[ --quit--{ pop --quit--} false { quitflag false --def---dict- /
execdepth 2 --copy----get--1 --sub----put----end---dict- /doclose false
--put--interrupt } --loop----cvx--[ /quitflag false --def---dict- /
newerror false --put--/prompt --load----stopped--{ (
Error during prompt execution
)--print--handleerror --exit--} --if--{
mark /stmtfile (%statementedit)(r)--file----def--} --stopped--{ --
cleartomark---dict- /newerror --get--{ -dict- /errorname --get--/
undefinedfilename --ne--{ handleerror } --if---dict- /newerror false --
put----exit--} --if--} { --pop--stmtfile --end--{ --cvx----exec--} --
stopped---dict- --begin--{ handleerror stmtfile --closefile--} --if--}
--ifelse--checkquit ] { checkquit } { -dict- --begin--{ handleerror
stmtfile --closefile--} --if--} false -file- -file- -file- --repeat----
cvx--[ randcurve randwidth randcolor stroke ] 1 { flushpage newpath } {
newpath } --forall----cvx--[ dup length 2 gt { [ currentcolordict DEVICE
/nativecolorspace get get exec counttomark 2 add -1 roll DEVICE dup /
FillPoly get exec pop pstack ()= flushpage } { pop } ifelse ] [ ] { pop
pstack ()= flushpage } { x_max width 0.50 add def (
intersect polygon edges with scanlines)= /P poly poly length 1 sub get
def [ poly { Q exch def x_max miny floor cvi 0.50 add 1 maxy ceiling cvi
0.50 sub { 1 index exch -0.50 1 index 4 2 roll P aload pop Q aload pop
.intersect { 2 array astore exch } if } for pop /P Q def } forall ] (
sort scanline intersection list)= dup { 1 index 1 get 1 index 1 get eq
{ exch 0 get exch 0 get lt } { exch 1 get exch 1 get lt } ifelse } qsort
(set pixels on each scanline)= aload length 2 idiv { exch aload pop 3 2
roll aload pop /USEDRAWLINE where { pop r g b 7 3 roll currentdict
DrawLine } { pop 3 2 roll exch 1 exch dup width ge { pop width 1 sub }
if { r g b 4 3 roll 2 index currentdict PutPix } for pop } ifelse }
repeat end } --forall----cvx--[ aload pop .maxmin ] [ [ 16 154 ] [ 16
154 ] ] { pop .maxmin } ]
PS<3>
Now most of it will probably be gibberish, and the top few pieces may not even be readable. This output is from my own postscript interpreter which is under construction and all objects have full access. But don't look at the top. Look at the bottom. The very last element of the array is the topmost element of the stack. It's the piece of code that would have come next had /command
not gone and done /errorname
. That little postscript fragment can help you locate where in the source the problem lies. In my case above, I need to search my source for calls to .maxmin
preceded by pop
preceded by .. whatever the error was.
executive
to get a promptIf you have a serial or telnet session with an interpreter in a printer, you can type executive
and hit enter a few times. It may not echo the letters of executive
as you type. Have no fear, but spell correctly. It should give you a greeting and a prompt.
With ghostscript, running the program with no arguments will give you the same sort of executive session. You can then (yourfile)run
and you should still get a prompt after errors, allowing you to inspect $error as described above.
If that doesn't work, you can try running executive
twice. This adds an extra level of error-handling (another stopped {handlerror} if
on the exec stack). This may help in hunting more bizarre errors.
I have available a source-level stepwise debugger that should run in any Level 2 compliant PostScript Interpreter.
It can also be used to generate stack traces as shown in this answer on TeX.SE.
On OS X 10.7.5, preview gives me no details when it bombs, but /usr/bin/pstopdf
does give me a stack dump to stdout and that's where pstack
goes, too.
If I open the pdf file in preview, then changing back to preview after running pstopdf
will refresh the view for the newly created pdf file.
It's not high-tech, but you can iterate pretty fast.
Emacs includes PostScript tools. It includes tools for sending the postscript interpreter your currently selected text, and you can also enter commands directly into that same interpreter, for, say, querying the operand stack, or things like that.
This might not be what you are looking for, though, because it might be harder to use than you are willing to work with. Set up correctly, though, with different buffers for all the things you want to monitor, scripts and macros to do things, etc, it will do everything you want it to. I am not sure, but there may be stuff elsewhere on the net that can help you set it up.
EDIT: The main way I have used Emacs to debug postscript is in doing the following:
I can copy-paste segments of the program from the file buffer into the interpreter buffer as a way to step through my program. I can also use it to tell me things about the operand stack, using commands to print out its contents and such. I can also add debugging statements to the code (like dup ==
etc.) that will output into the interpreter buffer, because I have been somewhat unable to figure out how to view stdout
as the program is executed using other environments.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With