1 module ddebug.gdb.gdbmiparser;
2 
3 import dlangui.core.logger;
4 import std.utf;
5 import std.conv : to;
6 import std.array : empty;
7 import std.algorithm : startsWith, equal;
8 import std.string : format;
9 import ddebug.common.debugger;
10 
11 /// result class
12 enum ResultClass {
13     done,
14     running,
15     connected,
16     error,
17     exit,
18     other
19 }
20 
21 /// async message class
22 enum AsyncClass {
23     running,
24     stopped,
25     library_loaded,
26     library_unloaded,
27     thread_group_added,
28     thread_group_started,
29     thread_group_exited,
30     thread_created,
31     thread_exited,
32     other
33 }
34 
35 /// Parse GDB MI output string
36 MIValue parseMI(string s) {
37     string src = s;
38     try {
39         bool err = false;
40         //Log.e("Tokenizing MI output: " ~ src);
41         MIToken[] tokens = tokenizeMI(s, err);
42         if (err) {
43             // tokenizer error
44             Log.e("Cannot tokenize MI output `" ~ src ~ "`");
45             return null;
46         }
47         //Log.v("Parsing tokens " ~ tokens.dumpTokens);
48         MIValue[] items = parseMIList(tokens);
49         //Log.v("Found " ~ to!string(items.length) ~ " values in list");
50         return new MIList(items);
51     } catch (Exception e) {
52         Log.e("Cannot parse MI output `" ~ src ~ "`", e.msg);
53         return null;
54     }
55 }
56 
57 string demangleFunctionName(string mangledName) {
58     import std.ascii;
59     if (!mangledName)
60         return mangledName;
61     string fn = mangledName;
62     if (!fn.startsWith("_D")) {
63         // trying to fix strange corrupted mangling under OSX/dmd/lldb
64     	if (fn.length < 3 || fn[0]!='D' || !isDigit(fn[1]))
65         	return mangledName;
66         fn = "_" ~ mangledName;
67     }
68     import std.demangle;
69     import std.ascii;
70     //import core.demangle : Demangle;
71     uint i = 0;
72     for(; i < fn.length && (fn[i] == '_' || isAlphaNum(fn[i])); i++) {
73         // next
74     }
75     string rest = i < fn.length ? fn[i .. $] : null;
76     try {
77         return demangle(fn[0..i]) ~ rest;
78     } catch (Exception e) {
79         // cannot demangle
80         Log.v("Failed to demangle " ~ fn[0..i]);
81         return mangledName;
82     }
83 }
84 
85 /*
86     frame = {
87             addr = "0x00000000004015b2",
88             func = "D main",
89             args = [],
90             file = "source\app.d",
91             fullname = "D:\projects\d\dlangide\workspaces\helloworld\helloworld/source\app.d",
92             line = "8"
93     },
94 */
95 DebugFrame parseFrame(MIValue frame) {
96     import std.path;
97     if (!frame)
98         return null;
99     DebugFrame location = new DebugFrame();
100     location.file = baseName(toNativeDelimiters(frame.getString("file")));
101     location.projectFilePath = toNativeDelimiters(frame.getString("file"));
102     location.fullFilePath = toNativeDelimiters(frame.getString("fullname"));
103     location.line = frame.getInt("line");
104     location.func = demangleFunctionName(frame.getString("func"));
105     location.address = frame.getUlong("addr");
106     location.from = toNativeDelimiters(frame.getString("from"));
107     location.level = frame.getInt("level");
108     return location;
109 }
110 
111 DebugVariableList parseVariableList(MIValue params, string paramName = "variables") {
112     if (!params)
113         return null;
114     DebugVariableList res = new DebugVariableList();
115     MIValue list = params[paramName];
116     if (list && list.isList) {
117         for(int i = 0; i < list.length; i++) {
118             if (DebugVariable t = parseVariable(list[i]))
119                 res.variables ~= t;
120         }
121     }
122     return res;
123 }
124 
125 DebugVariable parseVariable(MIValue params) {
126     if (!params)
127         return null;
128     DebugVariable res = new DebugVariable();
129     res.name = params.getString("name");
130     res.value = params.getString("value");
131     res.type = params.getString("type");
132     return res;
133 }
134 
135 DebugThreadList parseThreadList(MIValue params) {
136     if (!params)
137         return null;
138     DebugThreadList res = new DebugThreadList();
139     res.currentThreadId = params.getUlong("current-thread-id");
140     MIValue threads = params["threads"];
141     if (threads && threads.isList) {
142         for(int i = 0; i < threads.length; i++) {
143             if (DebugThread t = parseThread(threads[i]))
144                 res.threads ~= t;
145         }
146     }
147     return res;
148 }
149 
150 DebugThread parseThread(MIValue params) {
151     if (!params)
152         return null;
153     DebugThread res = new DebugThread();
154     res.id = params.getUlong("id");
155     res.name = params.getString("target-id");
156     if (res.name.empty)
157         res.name = "Thread%d".format(res.id);
158     string stateName = params.getString("state");
159     if (stateName == "stopped")
160         res.state = DebuggingState.paused;
161     else if (stateName == "running")
162         res.state = DebuggingState.running;
163     else
164         res.state = DebuggingState.stopped;
165     res.frame = parseFrame(params["frame"]);
166     return res;
167 }
168 
169 DebugStack parseStack(MIValue params) {
170     if (!params)
171         return null;
172     MIValue stack = params["stack"];
173     if (!stack)
174         return null;
175     DebugStack res = new DebugStack();
176     for (int i = 0; i < stack.length; i++) {
177         MIValue item = stack[i];
178         if (item && item.isKeyValue && item.key.equal("frame")) {
179             DebugFrame location = parseFrame(item.value);
180             if (location) {
181                 res.frames ~= location;
182             }
183         }
184     }
185     return res;
186 }
187 
188 string toNativeDelimiters(string s) {
189     version(Windows) {
190         char[] buf;
191         foreach(ch; s) {
192             if (ch == '/')
193                 buf ~= '\\';
194             else
195                 buf ~= ch;
196         }
197         return buf.dup;
198     } else {
199         return s;
200     }
201 }
202 
203 string parseIdent(ref string s) {
204     string res = null;
205     int len = 0;
206     for(; len < s.length; len++) {
207         char ch = s[len];
208         if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '-'))
209             break;
210     }
211     if (len > 0) {
212         res = s[0..len];
213         s = s[len .. $];
214     }
215     return res;
216 }
217 
218 bool skipComma(ref string s) {
219     if (s.length > 0 && s[0] == ',') {
220         s = s[1 .. $];
221         return true;
222     }
223     return false;
224 }
225 
226 string parseIdentAndSkipComma(ref string s) {
227     string res = parseIdent(s);
228     skipComma(s);
229     return res;
230 }
231 
232 ResultClass resultByName(string s) {
233     if (s.equal("done")) return ResultClass.done;
234     if (s.equal("running")) return ResultClass.running;
235     if (s.equal("connected")) return ResultClass.connected;
236     if (s.equal("error")) return ResultClass.error;
237     if (s.equal("exit")) return ResultClass.exit;
238     return ResultClass.other;
239 }
240 
241 AsyncClass asyncByName(string s) {
242     if (s.equal("stopped")) return AsyncClass.stopped;
243     if (s.equal("running")) return AsyncClass.running;
244     if (s.equal("library-loaded")) return AsyncClass.library_loaded;
245     if (s.equal("library-unloaded")) return AsyncClass.library_unloaded;
246     if (s.equal("thread-group-added")) return AsyncClass.thread_group_added;
247     if (s.equal("thread-group-started")) return AsyncClass.thread_group_started;
248     if (s.equal("thread-group-exited")) return AsyncClass.thread_group_exited;
249     if (s.equal("thread-created")) return AsyncClass.thread_created;
250     if (s.equal("thread-exited")) return AsyncClass.thread_exited;
251     return AsyncClass.other;
252 }
253 
254 enum MITokenType {
255     /// end of line
256     eol,
257     /// error
258     error,
259     /// identifier
260     ident,
261     /// C string
262     str,
263     /// = sign
264     eq,
265     /// , sign
266     comma,
267     /// { brace
268     curlyOpen,
269     /// } brace
270     curlyClose,
271     /// [ brace
272     squareOpen,
273     /// ] brace
274     squareClose,
275 }
276 
277 struct MIToken {
278     MITokenType type;
279     string str;
280     this(MITokenType type, string str = null) {
281         this.type = type;
282         this.str = str;
283     }
284     string toString() {
285         //return type == MITokenType.str ? to!string(type) ~ ":\"" ~ str ~ "\"": to!string(type) ~ ":" ~ str;
286         return (type == MITokenType.str) ? "\"" ~ str ~ "\"" : str;
287     }
288 }
289 
290 MIToken parseMIToken(ref string s) {
291     if (s.empty)
292         return MIToken(MITokenType.eol);
293     char ch = s[0];
294     if (ch == ',') {
295         s = s[1..$];
296         return MIToken(MITokenType.comma, ",");
297     }
298     if (ch == '=') {
299         s = s[1..$];
300         return MIToken(MITokenType.eq, "=");
301     }
302     if (ch == '{') {
303         s = s[1..$];
304         return MIToken(MITokenType.curlyOpen, "{");
305     }
306     if (ch == '}') {
307         s = s[1..$];
308         return MIToken(MITokenType.curlyClose, "}");
309     }
310     if (ch == '[') {
311         s = s[1..$];
312         return MIToken(MITokenType.squareOpen, "[");
313     }
314     if (ch == ']') {
315         s = s[1..$];
316         return MIToken(MITokenType.squareClose, "]");
317     }
318     // C string
319     if (ch == '\"') {
320         string str = parseCString(s);
321         if (!str.ptr) {
322             return MIToken(MITokenType.error);
323         }
324         return MIToken(MITokenType.str, str);
325     }
326     // identifier
327     string str = parseIdent(s);
328     if (!str.empty)
329         return MIToken(MITokenType.ident, str);
330     return MIToken(MITokenType.error);
331 }
332 
333 /// tokenize GDB MI output into array of tokens
334 MIToken[] tokenizeMI(string s, out bool error) {
335     error = false;
336     string src = s;
337     MIToken[] res;
338     for(;;) {
339         MIToken token = parseMIToken(s);
340         if (token.type == MITokenType.eol)
341             break;
342         if (token.type == MITokenType.error) {
343             error = true;
344             Log.e("Error while tokenizing GDB output ", src, " near ", s);
345             break;
346         }
347         res ~= token;
348     }
349     return res;
350 }
351 
352 string dumpTokens(MIToken[] tokens, int maxTokens = 40, int maxChars = 80) {
353     char[] buf;
354     for (int i = 0; i < maxTokens && i < tokens.length && buf.length < maxChars; i++) {
355         buf ~= tokens[i].toString;
356         buf ~= ' ';
357     }
358     return buf.dup;
359 }
360 
361 MIValue parseMIValue(ref MIToken[] tokens) {
362     MIToken[] srctokens;
363     if (tokens.length == 0)
364         throw new Exception("parseMIValue: Unexpected end of line when value is expected");
365     MITokenType tokenType = tokens.length > 0 ? tokens[0].type : MITokenType.eol;
366     MITokenType nextTokenType = tokens.length > 1 ? tokens[1].type : MITokenType.eol;
367     if (tokenType == MITokenType.ident) {
368         string ident = tokens[0].str;
369         if (nextTokenType == MITokenType.eol || nextTokenType == MITokenType.comma) {
370             MIValue res = new MIIdent(ident);
371             tokens = tokens[1..$];
372             return res;
373         } else if (nextTokenType == MITokenType.eq) {
374             tokens = tokens[1..$]; // skip ident
375             tokens = tokens[1..$]; // skip =
376             MIValue value = parseMIValue(tokens);
377             //tokens = tokens[1..$]; // skip value
378             MIValue res = new MIKeyValue(ident, value);
379             return res;
380         }
381         throw new Exception("parseMIValue: Unexpected token " ~ tokens[0].toString ~ " near " ~ srctokens.dumpTokens);
382     } else if (tokenType == MITokenType.str) {
383         string str = tokens[0].str;
384         tokens = tokens[1..$];
385         return new MIString(str);
386     } else if (tokenType == MITokenType.curlyOpen) {
387         tokens = tokens[1..$];
388         MIValue[] list = parseMIList(tokens, MITokenType.curlyClose);
389         return new MIMap(list);
390     } else if (tokenType == MITokenType.squareOpen) {
391         tokens = tokens[1..$];
392         MIValue[] list = parseMIList(tokens, MITokenType.squareClose);
393         return new MIList(list);
394     }
395     throw new Exception("parseMIValue: unexpected token " ~ tokens[0].toString ~ " near " ~ srctokens.dumpTokens);
396 }
397 
398 private MIValue[] parseMIList(ref MIToken[] tokens, MITokenType closingToken = MITokenType.eol) {
399     //Log.v("parseMIList: " ~ tokens.dumpTokens);
400     MIValue[] res;
401     if (!tokens.length)
402         return res;
403     for (;;) {
404         MITokenType tokenType = tokens.length > 0 ? tokens[0].type : MITokenType.eol;
405         if (tokenType == closingToken) {
406             if (tokenType != MITokenType.eol)
407                 tokens = tokens[1..$];
408             return res;
409         }
410         if (tokenType == MITokenType.eol)
411             throw new Exception("parseMIList: Unexpected eol in list");
412         if (res.length > 0) {
413             // comma required
414             if (tokenType != MITokenType.comma)
415                 throw new Exception("parseMIList: comma expected, found " ~ tokens[0].toString ~ " near " ~ tokens.dumpTokens);
416             tokens = tokens[1..$];
417         }
418         MIValue value = parseMIValue(tokens);
419         res ~= value;
420     }
421 }
422 
423 enum MIValueType {
424     /// ident
425     empty,
426     /// ident
427     ident,
428     /// c-string
429     str,
430     /// key=value pair
431     keyValue,
432     /// list []
433     list,
434     /// map {key=value, ...}
435     map,
436 }
437 
438 private void dumpLevel(ref char[] buf, int level) {
439     for (int i = 0; i < level; i++)
440         buf ~= "    ";
441 }
442 
443 class MIValue {
444     MIValueType type;
445     this(MIValueType type) {
446         this.type = type;
447     }
448     @property string str() { return null; }
449     @property int length() { return 0; }
450     MIValue opIndex(int index) { return null; }
451     MIValue opIndex(string key) { return null; }
452     @property bool isIdent() { return type == MIValueType.list; }
453     @property bool isString() { return type == MIValueType.str; }
454     @property bool isKeyValue() { return type == MIValueType.keyValue; }
455     @property bool isMap() { return type == MIValueType.map; }
456     @property bool isList() { return type == MIValueType.list; }
457     @property string key() { return str; }
458     @property MIValue value() { return this; }
459 
460     string getString(string name) {
461         MIValue v = opIndex(name);
462         if (!v)
463             return null;
464         return v.str;
465     }
466 
467     int getInt(string name, int defValue = 0) {
468         MIValue v = opIndex(name);
469         if (!v)
470             return defValue;
471         string s = v.str;
472         if (s.empty)
473             return defValue;
474         return cast(int)decodeNumber(s, defValue);
475     }
476 
477     ulong getUlong(string name, ulong defValue = 0) {
478         MIValue v = opIndex(name);
479         if (!v)
480             return defValue;
481         string s = v.str;
482         if (s.empty)
483             return defValue;
484         return decodeNumber(s, defValue);
485     }
486 
487     string getString(int index) {
488         MIValue v = opIndex(index);
489         if (!v)
490             return null;
491         return v.str;
492     }
493 
494     void dump(ref char[] buf, int level) {
495         //dumpLevel(buf, level);
496         buf ~= str;
497     }
498     override string toString() {
499         char[] buf;
500         dump(buf, 0);
501         return buf.dup;
502     }
503 }
504 
505 class MIKeyValue : MIValue {
506     private string _key;
507     private MIValue _value;
508     this(string key, MIValue value) {
509         super(MIValueType.keyValue);
510         _key = key;
511         _value = value;
512     }
513     override @property string key() { return _key; }
514     override @property MIValue value() { return _value; }
515     override @property string str() { return _key; }
516     override void dump(ref char[] buf, int level) {
517         //dumpLevel(buf, level);
518         buf ~= _key;
519         buf ~= " = ";
520         if (!value)
521             buf ~= "null";
522         else
523             _value.dump(buf, level + 1);
524     }
525 }
526 
527 class MIIdent : MIValue {
528     private string _ident;
529     this(string ident) {
530         super(MIValueType.ident);
531         _ident = ident;
532     }
533     override @property string str() { return _ident; }
534 }
535 
536 class MIString : MIValue {
537     private string _str;
538     this(string str) {
539         super(MIValueType.str);
540         _str = str;
541     }
542     override @property string str() { return _str; }
543     override void dump(ref char[] buf, int level) {
544         //dumpLevel(buf, level);
545         buf ~= '\"';
546         buf ~= str;
547         buf ~= '\"';
548     }
549 }
550 
551 class MIList : MIValue {
552     private MIValue[] _items;
553     private MIValue[string] _map;
554     
555     override @property int length() { return cast(int)_items.length; }
556     override MIValue opIndex(int index) { 
557         if (index < 0 || index >= _items.length)
558             return null;
559         return _items[index];
560     }
561     
562     override MIValue opIndex(string key) {
563         if (key in _map) {
564             MIValue res = _map[key];
565             return res;
566         }
567         return null; 
568     }
569     
570     this(MIValue[] items) {
571         super(MIValueType.list);
572         _items = items;
573         // fill map
574         foreach(item; _items) {
575             if (item.type == MIValueType.keyValue) {
576                 if (!item.str.empty)
577                     _map[item.str] = (cast(MIKeyValue)item).value;
578             }
579         }
580     }
581     override void dump(ref char[] buf, int level) {
582         buf ~= (type == MIValueType.map) ? "{" : "[";
583         if (length) {
584             buf ~= "\n";
585             for (int i = 0; i < length; i++) {
586                 dumpLevel(buf, level + 1);
587                 _items[i].dump(buf, level + 1);
588                 if (i < length - 1)
589                     buf ~= ",";
590                 buf ~= "\n";
591             }
592             //buf ~= "\n";
593             dumpLevel(buf, level - 1);
594         }
595         buf ~= (type == MIValueType.map) ? "}" : "]";
596     }
597 }
598 
599 class MIMap : MIList {
600     this(MIValue[] items) {
601         super(items);
602         type = MIValueType.map;
603     }
604 }
605 
606 private char nextChar(ref string s) {
607     if (s.empty)
608         return 0;
609     char ch = s[0];
610     s = s[1 .. $];
611     return ch;
612 }
613 
614 string parseCString(ref string s) {
615     char[] res;
616     // skip opening "
617     char ch = nextChar(s);
618     if (!ch)
619         return null;
620     if (ch != '\"')
621         return null;
622     for (;;) {
623         if (s.empty) {
624             // unexpected end of string
625             return null;
626         }
627         ch = nextChar(s);
628         if (ch == '\"')
629             break;
630         if (ch == '\\') {
631             // escape sequence
632             ch = nextChar(s);
633             if (ch >= '0' && ch <= '7') {
634                 // octal
635                 int number = (ch - '0');
636                 char ch2 = nextChar(s);
637                 char ch3 = nextChar(s);
638                 if (ch2 < '0' || ch2 > '7')
639                     return null;
640                 if (ch3 < '0' || ch3 > '7')
641                     return null;
642                 number = number * 8 + (ch2 - '0');
643                 number = number * 8 + (ch3 - '0');
644                 if (number > 255)
645                     return null; // invalid octal number
646                 res ~= cast(char)number;
647             } else {
648                 switch (ch) {
649                     case 'n':
650                         res ~= '\n';
651                         break;
652                     case 'r':
653                         res ~= '\r';
654                         break;
655                     case 't':
656                         res ~= '\t';
657                         break;
658                     case 'a':
659                         res ~= '\a';
660                         break;
661                     case 'b':
662                         res ~= '\b';
663                         break;
664                     case 'f':
665                         res ~= '\f';
666                         break;
667                     case 'v':
668                         res ~= '\v';
669                         break;
670                     case 'x': {
671                         // 2 digit hex number
672                         uint ch2 = decodeHexDigit(nextChar(s));
673                         uint ch3 = decodeHexDigit(nextChar(s));
674                         if (ch2 > 15 || ch3 > 15)
675                             return null;
676                         res ~= cast(char)((ch2 << 4) | ch3);
677                         break;
678                     }
679                     default:
680                         res ~= ch;
681                         break;
682                 }
683             }
684         } else {
685             res ~= ch;
686         }
687     }
688     if (!res.length)
689         return "";
690     return res.dup;
691 }
692 
693 /// decodes hex digit (0..9, a..f, A..F), returns uint.max if invalid
694 private uint decodeHexDigit(T)(T ch) {
695     if (ch >= '0' && ch <= '9')
696         return ch - '0';
697     else if (ch >= 'a' && ch <= 'f')
698         return ch - 'a' + 10;
699     else if (ch >= 'A' && ch <= 'F')
700         return ch - 'A' + 10;
701     return uint.max;
702 }
703 
704 private ulong decodeNumber(string s, ulong defValue) {
705     if (s.empty)
706         return defValue;
707     if (s.length > 2 && s.startsWith("0x")) {
708         s = s[2..$];
709         ulong res = 0;
710         foreach(ch; s) {
711             uint digit = decodeHexDigit(ch);
712             if (digit > 15)
713                 return defValue;
714             res = res * 16 + digit;
715         }
716         return res;
717     } else {
718         return to!ulong(s);
719     }
720 }