#!/usr/bin/perl

use strict;

#----------------------------------------------------------------------
# Globals
#----------------------------------------------------------------------
our $unimplemented_str = "UNIMPLEMENTED";
our $success_str = "OK";
our $swap = 1;
our $addr_size = 4;
our $thread_suffix_supported = 0;
our $max_bytes_per_line = 32;
our $addr_format = sprintf("0x%%%u.%ux", $addr_size*2, $addr_size*2);
our $pid_format = "%04.4x";
our $tid_format = "%04.4x";
our $reg8_href = { extract => \&get8, format => "0x%2.2x" };
our $reg16_href = { extract => \&get16, format => "0x%4.4x" };
our $reg32_href = { extract => \&get32, format => "0x%8.8x" };
our $reg64_href = { extract => \&get64, format => "0x%s" };
our $reg80_href = { extract => \&get80, format => "0x%s" };
our $reg128_href = { extract => \&get128, format => "0x%s" };
our $reg256_href = { extract => \&get256, format => "0x%s" };
our $float32_href = { extract => \&get32, format => "0x%8.8x" };
our $float64_href = { extract => \&get64, format => "0x%s" };
our $float96_href = { extract => \&get96, format => "0x%s" };
our $curr_cmd = undef;
our $curr_full_cmd = undef;
our %packet_times;
our $curr_time = 0.0;
our $last_time = 0.0;
our $base_time = 0.0;
our $packet_start_time = 0.0;
our $reg_cmd_reg;
our %reg_map = (
	'i386-gdb' => [ 
	    { name => 'eax',    info => $reg32_href     },
        { name => 'ecx',    info => $reg32_href     },
        { name => 'edx',    info => $reg32_href     },
        { name => 'ebx',    info => $reg32_href     },
        { name => 'esp',    info => $reg32_href     },
        { name => 'ebp',    info => $reg32_href     },
        { name => 'esi',    info => $reg32_href     },
        { name => 'edi',    info => $reg32_href     },
        { name => 'eip',    info => $reg32_href     },
        { name => 'eflags', info => $reg32_href     },
        { name => 'cs',     info => $reg32_href     },
        { name => 'ss',     info => $reg32_href     },
        { name => 'ds',     info => $reg32_href     },
        { name => 'es',     info => $reg32_href     },
        { name => 'fs',     info => $reg32_href     },
        { name => 'gs',     info => $reg32_href     },
        { name => 'st0',    info => $reg80_href     },
        { name => 'st1',    info => $reg80_href     },
        { name => 'st2',    info => $reg80_href     },
        { name => 'st3',    info => $reg80_href     },
        { name => 'st4',    info => $reg80_href     },
        { name => 'st5',    info => $reg80_href     },
        { name => 'st6',    info => $reg80_href     },
        { name => 'st7',    info => $reg80_href     },
        { name => 'fctrl',  info => $reg32_href     },
        { name => 'fstat',  info => $reg32_href     },
        { name => 'ftag',   info => $reg32_href     },
        { name => 'fiseg',  info => $reg32_href     },
        { name => 'fioff',  info => $reg32_href     },
        { name => 'foseg',  info => $reg32_href     },
        { name => 'fooff',  info => $reg32_href     },
        { name => 'fop',    info => $reg32_href     },
    	{ name => 'xmm0',   info => $reg128_href    },
    	{ name => 'xmm1',   info => $reg128_href    },
    	{ name => 'xmm2',   info => $reg128_href    },
    	{ name => 'xmm3',   info => $reg128_href    },
    	{ name => 'xmm4',   info => $reg128_href    },
    	{ name => 'xmm5',   info => $reg128_href    },
    	{ name => 'xmm6',   info => $reg128_href    },
    	{ name => 'xmm7',   info => $reg128_href    },
    	{ name => 'mxcsr',  info => $reg32_href     },
        { name => 'mm0',    info => $reg64_href     },
        { name => 'mm1',    info => $reg64_href     },
        { name => 'mm2',    info => $reg64_href     },
        { name => 'mm3',    info => $reg64_href     },
        { name => 'mm4',    info => $reg64_href     },
        { name => 'mm5',    info => $reg64_href     },
        { name => 'mm6',    info => $reg64_href     },
        { name => 'mm7',    info => $reg64_href     },
    ],
    
    'i386-lldb' => [
        { name => 'eax',          info => $reg32_href   },
        { name => 'ebx',          info => $reg32_href   },
        { name => 'ecx',          info => $reg32_href   },
        { name => 'edx',          info => $reg32_href   },
        { name => 'edi',          info => $reg32_href   },
        { name => 'esi',          info => $reg32_href   },
        { name => 'ebp',          info => $reg32_href   },
        { name => 'esp',          info => $reg32_href   },
        { name => 'ss',           info => $reg32_href   },
        { name => 'eflags',       info => $reg32_href   },
        { name => 'eip',          info => $reg32_href   },
        { name => 'cs',           info => $reg32_href   },
        { name => 'ds',           info => $reg32_href   },
        { name => 'es',           info => $reg32_href   },
        { name => 'fs',           info => $reg32_href   },
        { name => 'gs',           info => $reg32_href   },
    	{ name => 'fctrl',        info => $reg16_href   },
    	{ name => 'fstat',        info => $reg16_href   },
    	{ name => 'ftag',         info => $reg8_href    },
    	{ name => 'fop',          info => $reg16_href   },
    	{ name => 'fioff',        info => $reg32_href   },
    	{ name => 'fiseg',        info => $reg16_href   },
    	{ name => 'fooff',        info => $reg32_href   },
    	{ name => 'foseg',        info => $reg16_href   },
    	{ name => 'mxcsr',        info => $reg32_href   },
    	{ name => 'mxcsrmask',    info => $reg32_href   },
    	{ name => 'stmm0',        info => $reg80_href   },
    	{ name => 'stmm1',        info => $reg80_href   },
    	{ name => 'stmm2',        info => $reg80_href   },
    	{ name => 'stmm3',        info => $reg80_href   },
    	{ name => 'stmm4',        info => $reg80_href   },
    	{ name => 'stmm5',        info => $reg80_href   },
    	{ name => 'stmm6',        info => $reg80_href   },
    	{ name => 'stmm7',        info => $reg80_href   },
    	{ name => 'xmm0',         info => $reg128_href  },
    	{ name => 'xmm1',         info => $reg128_href  },
    	{ name => 'xmm2',         info => $reg128_href  },
    	{ name => 'xmm3',         info => $reg128_href  },
    	{ name => 'xmm4',         info => $reg128_href  },
    	{ name => 'xmm5',         info => $reg128_href  },
    	{ name => 'xmm6',         info => $reg128_href  },
    	{ name => 'xmm7',         info => $reg128_href  },
    	{ name => 'trapno',       info => $reg32_href   },
    	{ name => 'err',          info => $reg32_href   },
    	{ name => 'faultvaddr',   info => $reg32_href   },
    ],
    
    'arm-gdb' => [
        { name => 'r0'      , info => $reg32_href   },
        { name => 'r1'      , info => $reg32_href   },
        { name => 'r2'      , info => $reg32_href   },
        { name => 'r3'      , info => $reg32_href   },
        { name => 'r4'      , info => $reg32_href   },
        { name => 'r5'      , info => $reg32_href   },
        { name => 'r6'      , info => $reg32_href   },
        { name => 'r7'      , info => $reg32_href   },
        { name => 'r8'      , info => $reg32_href   },
        { name => 'r9'      , info => $reg32_href   },
        { name => 'r10'     , info => $reg32_href   },
        { name => 'r11'     , info => $reg32_href   },
        { name => 'r12'     , info => $reg32_href   },
        { name => 'sp'      , info => $reg32_href   },
        { name => 'lr'      , info => $reg32_href   },
        { name => 'pc'      , info => $reg32_href   },
        { name => 'f0'      , info => $float96_href },
        { name => 'f1'      , info => $float96_href },
        { name => 'f2'      , info => $float96_href },
        { name => 'f3'      , info => $float96_href },
        { name => 'f4'      , info => $float96_href },
        { name => 'f5'      , info => $float96_href },
        { name => 'f6'      , info => $float96_href },
        { name => 'f7'      , info => $float96_href },
        { name => 'fps'     , info => $reg32_href   },
        { name => 'cpsr'    , info => $reg32_href   },
        { name => 's0'      , info => $float32_href },
        { name => 's1'      , info => $float32_href },
        { name => 's2'      , info => $float32_href },
        { name => 's3'      , info => $float32_href },
        { name => 's4'      , info => $float32_href },
        { name => 's5'      , info => $float32_href },
        { name => 's6'      , info => $float32_href },
        { name => 's7'      , info => $float32_href },
        { name => 's8'      , info => $float32_href },
        { name => 's9'      , info => $float32_href },
        { name => 's10'     , info => $float32_href },
        { name => 's11'     , info => $float32_href },
        { name => 's12'     , info => $float32_href },
        { name => 's13'     , info => $float32_href },
        { name => 's14'     , info => $float32_href },
        { name => 's15'     , info => $float32_href },
        { name => 's16'     , info => $float32_href },
        { name => 's17'     , info => $float32_href },
        { name => 's18'     , info => $float32_href },
        { name => 's19'     , info => $float32_href },
        { name => 's20'     , info => $float32_href },
        { name => 's21'     , info => $float32_href },
        { name => 's22'     , info => $float32_href },
        { name => 's23'     , info => $float32_href }, 
        { name => 's24'     , info => $float32_href },
        { name => 's25'     , info => $float32_href },
        { name => 's26'     , info => $float32_href },
        { name => 's27'     , info => $float32_href },
        { name => 's28'     , info => $float32_href },
        { name => 's29'     , info => $float32_href },
        { name => 's30'     , info => $float32_href },
        { name => 's31'     , info => $float32_href },
        { name => 'fpscr'   , info => $reg32_href   },
        { name => 'd16'     , info => $float64_href },
        { name => 'd17'     , info => $float64_href },
        { name => 'd18'     , info => $float64_href },
        { name => 'd19'     , info => $float64_href },
        { name => 'd20'     , info => $float64_href },
        { name => 'd21'     , info => $float64_href },
        { name => 'd22'     , info => $float64_href },
        { name => 'd23'     , info => $float64_href }, 
        { name => 'd24'     , info => $float64_href },
        { name => 'd25'     , info => $float64_href },
        { name => 'd26'     , info => $float64_href },
        { name => 'd27'     , info => $float64_href },
        { name => 'd28'     , info => $float64_href },
        { name => 'd29'     , info => $float64_href },
        { name => 'd30'     , info => $float64_href },
        { name => 'd31'     , info => $float64_href },
    ],
    
    
    'arm-lldb' => [
        { name => 'r0'      , info => $reg32_href   },
        { name => 'r1'      , info => $reg32_href   },
        { name => 'r2'      , info => $reg32_href   },
        { name => 'r3'      , info => $reg32_href   },
        { name => 'r4'      , info => $reg32_href   },
        { name => 'r5'      , info => $reg32_href   },
        { name => 'r6'      , info => $reg32_href   },
        { name => 'r7'      , info => $reg32_href   },
        { name => 'r8'      , info => $reg32_href   },
        { name => 'r9'      , info => $reg32_href   },
        { name => 'r10'     , info => $reg32_href   },
        { name => 'r11'     , info => $reg32_href   },
        { name => 'r12'     , info => $reg32_href   },
        { name => 'sp'      , info => $reg32_href   },
        { name => 'lr'      , info => $reg32_href   },
        { name => 'pc'      , info => $reg32_href   },
        { name => 'cpsr'    , info => $reg32_href   },
        { name => 's0'      , info => $float32_href },
        { name => 's1'      , info => $float32_href },
        { name => 's2'      , info => $float32_href },
        { name => 's3'      , info => $float32_href },
        { name => 's4'      , info => $float32_href },
        { name => 's5'      , info => $float32_href },
        { name => 's6'      , info => $float32_href },
        { name => 's7'      , info => $float32_href },
        { name => 's8'      , info => $float32_href },
        { name => 's9'      , info => $float32_href },
        { name => 's10'     , info => $float32_href },
        { name => 's11'     , info => $float32_href },
        { name => 's12'     , info => $float32_href },
        { name => 's13'     , info => $float32_href },
        { name => 's14'     , info => $float32_href },
        { name => 's15'     , info => $float32_href },
        { name => 's16'     , info => $float32_href },
        { name => 's17'     , info => $float32_href },
        { name => 's18'     , info => $float32_href },
        { name => 's19'     , info => $float32_href },
        { name => 's20'     , info => $float32_href },
        { name => 's21'     , info => $float32_href },
        { name => 's22'     , info => $float32_href },
        { name => 's23'     , info => $float32_href }, 
        { name => 's24'     , info => $float32_href },
        { name => 's25'     , info => $float32_href },
        { name => 's26'     , info => $float32_href },
        { name => 's27'     , info => $float32_href },
        { name => 's28'     , info => $float32_href },
        { name => 's29'     , info => $float32_href },
        { name => 's30'     , info => $float32_href },
        { name => 's31'     , info => $float32_href },
        { name => 'd0'      , info => $float64_href },
        { name => 'd1'      , info => $float64_href },
        { name => 'd2'      , info => $float64_href },
        { name => 'd3'      , info => $float64_href },
        { name => 'd4'      , info => $float64_href },
        { name => 'd5'      , info => $float64_href },
        { name => 'd6'      , info => $float64_href },
        { name => 'd7'      , info => $float64_href },
        { name => 'd8'      , info => $float64_href },
        { name => 'd9'      , info => $float64_href },
        { name => 'd10'     , info => $float64_href },
        { name => 'd11'     , info => $float64_href },
        { name => 'd12'     , info => $float64_href },
        { name => 'd13'     , info => $float64_href },
        { name => 'd14'     , info => $float64_href },
        { name => 'd15'     , info => $float64_href },
        { name => 'd16'     , info => $float64_href },
        { name => 'd17'     , info => $float64_href },
        { name => 'd18'     , info => $float64_href },
        { name => 'd19'     , info => $float64_href },
        { name => 'd20'     , info => $float64_href },
        { name => 'd21'     , info => $float64_href },
        { name => 'd22'     , info => $float64_href },
        { name => 'd23'     , info => $float64_href }, 
        { name => 'd24'     , info => $float64_href },
        { name => 'd25'     , info => $float64_href },
        { name => 'd26'     , info => $float64_href },
        { name => 'd27'     , info => $float64_href },
        { name => 'd28'     , info => $float64_href },
        { name => 'd29'     , info => $float64_href },
        { name => 'd30'     , info => $float64_href },
        { name => 'd31'     , info => $float64_href },
        { name => 'fpscr'   , info => $reg32_href   },
        { name => 'exc'     , info => $reg32_href   },
        { name => 'fsr'     , info => $reg32_href   },
        { name => 'far'     , info => $reg32_href   },
    ],    
    
    'x86_64-gdb' => [
    	{ name => 'rax'		, info => $reg64_href   },
    	{ name => 'rbx'     , info => $reg64_href   },
    	{ name => 'rcx'     , info => $reg64_href   },
    	{ name => 'rdx'     , info => $reg64_href   },
    	{ name => 'rsi'     , info => $reg64_href   },
    	{ name => 'rdi'     , info => $reg64_href   },
    	{ name => 'rbp'     , info => $reg64_href   },
    	{ name => 'rsp'     , info => $reg64_href   },
    	{ name => 'r8'      , info => $reg64_href   },
    	{ name => 'r9'      , info => $reg64_href   },
    	{ name => 'r10'     , info => $reg64_href   },
    	{ name => 'r11'     , info => $reg64_href   },
    	{ name => 'r12'     , info => $reg64_href   },
    	{ name => 'r13'     , info => $reg64_href   },
    	{ name => 'r14'     , info => $reg64_href   },
    	{ name => 'r15'     , info => $reg64_href   },
    	{ name => 'rip'     , info => $reg64_href   },
    	{ name => 'eflags'  , info => $reg32_href   },
    	{ name => 'cs'      , info => $reg32_href   },
    	{ name => 'ss'      , info => $reg32_href   },
    	{ name => 'ds'      , info => $reg32_href   },
    	{ name => 'es'      , info => $reg32_href   },
    	{ name => 'fs'      , info => $reg32_href   },
    	{ name => 'gs'      , info => $reg32_href   },
    	{ name => 'stmm0'   , info => $reg80_href   },
    	{ name => 'stmm1'   , info => $reg80_href   },
    	{ name => 'stmm2'   , info => $reg80_href   },
    	{ name => 'stmm3'   , info => $reg80_href   },
    	{ name => 'stmm4'   , info => $reg80_href   },
    	{ name => 'stmm5'   , info => $reg80_href   },
    	{ name => 'stmm6'   , info => $reg80_href   },
    	{ name => 'stmm7'   , info => $reg80_href   },
    	{ name => 'fctrl'   , info => $reg32_href   },
    	{ name => 'fstat'   , info => $reg32_href   },
    	{ name => 'ftag'    , info => $reg32_href   },
    	{ name => 'fiseg'   , info => $reg32_href   },
    	{ name => 'fioff'   , info => $reg32_href   },
    	{ name => 'foseg'   , info => $reg32_href   },
    	{ name => 'fooff'   , info => $reg32_href   },      
    	{ name => 'fop'     , info => $reg32_href   },
    	{ name => 'xmm0'	, info => $reg128_href  },
    	{ name => 'xmm1'    , info => $reg128_href  },
    	{ name => 'xmm2'    , info => $reg128_href  },
    	{ name => 'xmm3'    , info => $reg128_href  },
    	{ name => 'xmm4'    , info => $reg128_href  },
    	{ name => 'xmm5'    , info => $reg128_href  },
    	{ name => 'xmm6'    , info => $reg128_href  },
    	{ name => 'xmm7'    , info => $reg128_href  },
    	{ name => 'xmm8'    , info => $reg128_href  },
    	{ name => 'xmm9'    , info => $reg128_href  },
    	{ name => 'xmm10'   , info => $reg128_href  },
    	{ name => 'xmm11'   , info => $reg128_href  },
    	{ name => 'xmm12'   , info => $reg128_href  },
    	{ name => 'xmm13'   , info => $reg128_href  },
    	{ name => 'xmm14'   , info => $reg128_href  },
    	{ name => 'xmm15'   , info => $reg128_href	},
    	{ name => 'mxcsr'   , info => $reg32_href	},
    ],

    'x86_64-lldb' => [
        { name => 'rax'		    , info => $reg64_href   },
        { name => 'rbx'		    , info => $reg64_href   },
        { name => 'rcx'		    , info => $reg64_href   },
        { name => 'rdx'		    , info => $reg64_href   },
        { name => 'rdi'		    , info => $reg64_href   },
        { name => 'rsi'		    , info => $reg64_href   },
        { name => 'rbp'		    , info => $reg64_href   },
        { name => 'rsp'		    , info => $reg64_href   },
        { name => 'r8 '		    , info => $reg64_href   },
        { name => 'r9 '		    , info => $reg64_href   },
        { name => 'r10'		    , info => $reg64_href   },
        { name => 'r11'		    , info => $reg64_href   },
        { name => 'r12'		    , info => $reg64_href   },
        { name => 'r13'		    , info => $reg64_href   },
        { name => 'r14'		    , info => $reg64_href   },
        { name => 'r15'		    , info => $reg64_href   },
        { name => 'rip'		    , info => $reg64_href   },
        { name => 'rflags'	    , info => $reg64_href   },
        { name => 'cs'		    , info => $reg64_href   },
        { name => 'fs'		    , info => $reg64_href   },
        { name => 'gs'		    , info => $reg64_href   },
        { name => 'fctrl'       , info => $reg16_href   },
        { name => 'fstat'       , info => $reg16_href   },
        { name => 'ftag'        , info => $reg8_href    },
        { name => 'fop'         , info => $reg16_href   },
        { name => 'fioff'       , info => $reg32_href   },
        { name => 'fiseg'       , info => $reg16_href   },
        { name => 'fooff'       , info => $reg32_href   },
        { name => 'foseg'       , info => $reg16_href   },
        { name => 'mxcsr'       , info => $reg32_href   },
        { name => 'mxcsrmask'   , info => $reg32_href   },
        { name => 'stmm0'       , info => $reg80_href   },
        { name => 'stmm1'       , info => $reg80_href   },
        { name => 'stmm2'       , info => $reg80_href   },
        { name => 'stmm3'       , info => $reg80_href   },
        { name => 'stmm4'       , info => $reg80_href   },
        { name => 'stmm5'       , info => $reg80_href   },
        { name => 'stmm6'       , info => $reg80_href   },
        { name => 'stmm7'       , info => $reg80_href   },
        { name => 'xmm0'	    , info => $reg128_href  },
        { name => 'xmm1'	    , info => $reg128_href  },
        { name => 'xmm2'	    , info => $reg128_href  },
        { name => 'xmm3'	    , info => $reg128_href  },
        { name => 'xmm4'	    , info => $reg128_href  },
        { name => 'xmm5'	    , info => $reg128_href  },
        { name => 'xmm6'	    , info => $reg128_href  },
        { name => 'xmm7'	    , info => $reg128_href  },
        { name => 'xmm8'	    , info => $reg128_href  },
        { name => 'xmm9'	    , info => $reg128_href  },
        { name => 'xmm10'	    , info => $reg128_href  },
        { name => 'xmm11'	    , info => $reg128_href  },
        { name => 'xmm12'	    , info => $reg128_href  },
        { name => 'xmm13'	    , info => $reg128_href  },
        { name => 'xmm14'	    , info => $reg128_href  },
        { name => 'xmm15'	    , info => $reg128_href  },
        { name => 'trapno'      , info => $reg32_href   },
        { name => 'err'         , info => $reg32_href   },
        { name => 'faultvaddr'	, info => $reg64_href   },
    ]
);

our $max_register_name_len = 0;
calculate_max_register_name_length();
our @point_types = ( "software_bp", "hardware_bp", "write_wp", "read_wp", "access_wp" );
our $opt_v = 0;	# verbose
our $opt_g = 0;	# debug
our $opt_q = 0;	# quiet
our $opt_r = undef;
use Getopt::Std;
getopts('gvqr:'); 

our $registers_aref = undef;

if (length($opt_r))
{
	if (exists $reg_map{$opt_r})
	{
	    $registers_aref = $reg_map{$opt_r};		
	}
	else
	{
		die "Can't get registers group for '$opt_r'\n";
	}
}

sub extract_key_value_pairs 
{
    my $kv_href = {};
    my $arrayref = shift;
    my $str = join('',@$arrayref);
    my @kv_strs = split(/;/, $str);
    foreach my $kv_str (@kv_strs)
    {
        my ($key, $value) = split(/:/, $kv_str);
        $kv_href->{$key} = $value;
    }
    return $kv_href;
}

sub get_thread_from_thread_suffix
{
    if ($thread_suffix_supported)
    {
        my $arrayref = shift;
        # Skip leading semi-colon if needed
        $$arrayref[0] == ';' and shift @$arrayref;
        my $thread_href = extract_key_value_pairs ($arrayref);
        if (exists $thread_href->{thread})
        {
            return $thread_href->{thread};
        }
    }
    return undef;
}

sub calculate_max_register_name_length
{
	$max_register_name_len = 7;
	foreach my $reg_href (@$registers_aref)
	{
		my $name_len = length($reg_href->{name});
		if ($max_register_name_len < $name_len)
		{
			$max_register_name_len = $name_len;			
		}
	}
}
#----------------------------------------------------------------------
# Hash that maps command characters to the appropriate functions using
# the command character as the key and the value being a reference to
# the dump function for dumping the command itself.
#----------------------------------------------------------------------
our %cmd_callbacks = 
(
	'?' => \&dump_last_signal_cmd,
	'H' => \&dump_set_thread_cmd,
	'T' => \&dump_thread_is_alive_cmd,
	'q' => \&dump_general_query_cmd,
	'Q' => \&dump_general_set_cmd,
	'g' => \&dump_read_regs_cmd,
	'G' => \&dump_write_regs_cmd,
	'p' => \&dump_read_single_register_cmd,
	'P' => \&dump_write_single_register_cmd,	
	'm' => \&dump_read_mem_cmd,
	'M' => \&dump_write_mem_cmd,
	'X' => \&dump_write_mem_binary_cmd,
	'Z' => \&dump_bp_wp_command,
	'z' => \&dump_bp_wp_command,
	'k' => \&dump_kill_cmd,
	'A' => \&dump_A_command,
	'c' => \&dump_continue_cmd,
	's' => \&dump_continue_cmd,
	'C' => \&dump_continue_with_signal_cmd,
	'S' => \&dump_continue_with_signal_cmd,
	'_M' => \&dump_allocate_memory_cmd,
	'_m' => \&dump_deallocate_memory_cmd,
	# extended commands
	'v' => \&dump_extended_cmd
);

#----------------------------------------------------------------------
# Hash that maps command characters to the appropriate functions using
# the command character as the key and the value being a reference to
# the dump function for the response to the command.
#----------------------------------------------------------------------
our %rsp_callbacks = 
(
	'c' => \&dump_stop_reply_packet,
	's' => \&dump_stop_reply_packet,
	'C' => \&dump_stop_reply_packet,
	'?' => \&dump_stop_reply_packet,
	'T' => \&dump_thread_is_alive_rsp,
	'H' => \&dump_set_thread_rsp,
	'q' => \&dump_general_query_rsp,
	'g' => \&dump_read_regs_rsp,
	'p' => \&dump_read_single_register_rsp,
	'm' => \&dump_read_mem_rsp,
	'_M' => \&dump_allocate_memory_rsp,

	# extended commands
	'v' => \&dump_extended_rsp,
);


sub dump_register_value
{
    my $indent = shift;
	my $arrayref = shift;
	my $reg_num = shift;

    if ($reg_num >= @$registers_aref)
    {
        printf("\tinvalid register index %d\n", $reg_num);
        return;
    }
    
    my $reg_href = $$registers_aref[$reg_num];
    my $reg_name = $reg_href->{name};
	if ($$arrayref[0] eq '#')
	{
        printf("\t%*s: error: EOS reached when trying to read register %d\n", $max_register_name_len, $reg_name, $reg_num);
	}
	
    my $reg_info = $reg_href->{info};
    my $reg_extract = $reg_info->{extract};
    my $reg_format = $reg_info->{format};
    my $reg_val = &$reg_extract($arrayref);
    if ($indent) {
    	printf("\t%*s = $reg_format", $max_register_name_len, $reg_name, $reg_val);        
    } else {
    	printf("%s = $reg_format", $reg_name, $reg_val);        
    }
}

#----------------------------------------------------------------------
# Extract the command into an array of ASCII char strings for easy
# processing
#----------------------------------------------------------------------
sub extract_command
{
	my $cmd_str = shift;
	my @cmd_chars = split(/ */, $cmd_str);
	if ($cmd_chars[0] ne '$')
	{
		# only set the current command if it isn't a reply
		$curr_cmd = $cmd_chars[0]; 
	}
	return @cmd_chars;
}

#----------------------------------------------------------------------
# Strip the 3 checksum array entries after we don't need them anymore
#----------------------------------------------------------------------
sub strip_checksum
{
	my $arrayref = shift;
	splice(@$arrayref, -3);
}

#----------------------------------------------------------------------
# Dump all strings in array by joining them together with no space 
# between them
#----------------------------------------------------------------------
sub dump_chars
{
	print join('',@_);
}

#----------------------------------------------------------------------
# Check if the response is an error 'EXX'
#----------------------------------------------------------------------
sub is_error_response
{
	if ($_[0] eq 'E')
	{
		shift;
		print "ERROR = " . join('',@_) . "\n";
		return 1;
	}
	return 0;
}

#----------------------------------------------------------------------
# 'H' command
#----------------------------------------------------------------------
sub dump_set_thread_cmd
{
	my $cmd = shift;
	my $mod = shift;
	print "set_thread ( $mod, " . join('',@_) . " )\n";
}

#----------------------------------------------------------------------
# 'T' command
#----------------------------------------------------------------------
our $T_cmd_tid = -1;
sub dump_thread_is_alive_cmd
{
	my $cmd = shift;
	$T_cmd_tid = get_hex(\@_);
	printf("thread_is_alive ( $tid_format )\n", $T_cmd_tid);
}

sub dump_thread_is_alive_rsp
{
	my $rsp = join('',@_);
	
	printf("thread_is_alive ( $tid_format ) =>", $T_cmd_tid);
	if ($rsp eq 'OK')
	{
		print " alive.\n";
	}
	else
	{
		print " dead.\n";
	}
}

#----------------------------------------------------------------------
# 'H' response
#----------------------------------------------------------------------
sub dump_set_thread_rsp
{
	if (!is_error_response(@_))
	{
		print join('',@_) . "\n";
	}
}

#----------------------------------------------------------------------
# 'q' command
#----------------------------------------------------------------------
our $gen_query_cmd;
our $qRegisterInfo_reg_num = -1;
sub dump_general_query_cmd
{
	$gen_query_cmd = join('',@_);
	if ($gen_query_cmd eq 'qC')
	{
		print 'get_current_pid ()';
	}
	elsif ($gen_query_cmd eq 'qfThreadInfo')
	{
		print 'get_first_active_threads ()';
	}
	elsif ($gen_query_cmd eq 'qsThreadInfo')
	{
		print 'get_subsequent_active_threads ()';
	}
	elsif (index($gen_query_cmd, 'qThreadExtraInfo') == 0)
	{
		# qThreadExtraInfo,id
		print 'get_thread_extra_info ()';
	}
	elsif (index($gen_query_cmd, 'qThreadStopInfo') == 0)
	{
		# qThreadStopInfoXXXX
		@_ = splice(@_, length('qThreadStopInfo'));
		my $tid = get_addr(\@_);
		printf('get_thread_stop_info ( thread = 0x%4.4x )', $tid);
	}
	elsif (index($gen_query_cmd, 'qSymbol:') == 0)
	{
		# qCRC:addr,length
		print 'gdb_ready_to_serve_symbol_lookups ()';
	}
	elsif (index($gen_query_cmd, 'qCRC:') == 0)
	{
		# qCRC:addr,length
		@_ = splice(@_, length('qCRC:'));
		my $address = get_addr(\@_);
		shift @_;
		my $length = join('', @_);
		printf("compute_crc (addr = $addr_format, length = $length)", $address);
	}
	elsif (index($gen_query_cmd, 'qGetTLSAddr:') == 0)
	{
		# qGetTLSAddr:thread-id,offset,lm
		@_ = splice(@_, length('qGetTLSAddr:'));
		my ($tid, $offset, $lm) = split (/,/, join('', @_));
		print "get_thread_local_storage_addr (thread-id = $tid, offset = $offset, lm = $lm)";
	}
	elsif ($gen_query_cmd eq 'qOffsets')
	{
		print 'get_section_offsets ()';
	}
	elsif (index($gen_query_cmd, 'qRegisterInfo') == 0)
	{
		@_ = splice(@_, length('qRegisterInfo'));
		$qRegisterInfo_reg_num = get_hex(\@_);
		
		printf "get_dynamic_register_info ($qRegisterInfo_reg_num)";
	}
	else
	{
		print $gen_query_cmd;
	}
	print "\n";
}

#----------------------------------------------------------------------
# 'q' response
#----------------------------------------------------------------------
sub dump_general_query_rsp
{
	my $gen_query_rsp = join('',@_);
	my $gen_query_rsp_len = length ($gen_query_rsp);
	if ($gen_query_cmd eq 'qC' and index($gen_query_rsp, 'QC') == 0)
	{
		shift @_; shift @_;
		my $pid = get_hex(\@_);
		printf("pid = $pid_format\n", $pid);
		return;
	}
	elsif (index($gen_query_cmd, 'qRegisterInfo') == 0)
	{
		if ($gen_query_rsp_len == 0)
		{
			print "$unimplemented_str\n";			
		}
		else
		{
			if (index($gen_query_rsp, 'name') == 0)
			{
				$qRegisterInfo_reg_num == 0 and $registers_aref = [];

				my @name_and_values = split (/;/, $gen_query_rsp);
			
				my $reg_name = undef;
				my $byte_size = 0;
				my $pseudo = 0;
				foreach (@name_and_values)
				{
					my ($name, $value) = split /:/;				
					if    ($name eq "name") { $reg_name = $value; }
					elsif ($name eq "bitsize") { $byte_size = $value / 8; }
					elsif ($name eq "container-regs") { $pseudo = 1; }
				}
				if (defined $reg_name and $byte_size > 0)
				{
					if    ($byte_size == 4)  {push @$registers_aref, { name => $reg_name, info => $reg32_href   , pseudo => $pseudo };}
					elsif ($byte_size == 8)  {push @$registers_aref, { name => $reg_name, info => $reg64_href   , pseudo => $pseudo };}
					elsif ($byte_size == 1)  {push @$registers_aref, { name => $reg_name, info => $reg8_href    , pseudo => $pseudo };}
					elsif ($byte_size == 2)  {push @$registers_aref, { name => $reg_name, info => $reg16_href   , pseudo => $pseudo };}
					elsif ($byte_size == 10) {push @$registers_aref, { name => $reg_name, info => $reg80_href   , pseudo => $pseudo };}
					elsif ($byte_size == 12) {push @$registers_aref, { name => $reg_name, info => $float96_href , pseudo => $pseudo };}
					elsif ($byte_size == 16) {push @$registers_aref, { name => $reg_name, info => $reg128_href  , pseudo => $pseudo };}
					elsif ($byte_size == 32) {push @$registers_aref, { name => $reg_name, info => $reg256_href  , pseudo => $pseudo };}
				}
			}
			elsif ($gen_query_rsp_len == 3 and index($gen_query_rsp, 'E') == 0)
			{
				calculate_max_register_name_length();
			}
		}
	}
	elsif ($gen_query_cmd =~ 'qThreadStopInfo')
	{
		dump_stop_reply_packet (@_);
	}
	if (dump_standard_response(\@_))
	{
		# Do nothing...
	}
	else
	{
		print join('',@_) . "\n";
	}
}

#----------------------------------------------------------------------
# 'Q' command
#----------------------------------------------------------------------
our $gen_set_cmd;
sub dump_general_set_cmd
{
	$gen_query_cmd = join('',@_);
	if ($gen_query_cmd eq 'QStartNoAckMode')
	{
		print "StartNoAckMode ()"
	}
	elsif ($gen_query_cmd eq 'QThreadSuffixSupported')
	{
	    $thread_suffix_supported = 1;
		print "ThreadSuffixSupported ()"
	}
	elsif (index($gen_query_cmd, 'QSetMaxPayloadSize:') == 0)
	{
		@_ = splice(@_, length('QSetMaxPayloadSize:'));
		my $max_payload_size = get_hex(\@_);
		# QSetMaxPayloadSize:XXXX  where XXXX is a hex length of the max
		# packet payload size supported by gdb
		printf("SetMaxPayloadSize ( 0x%x (%u))", $max_payload_size, $max_payload_size);
	}
	elsif (index ($gen_query_cmd, 'QSetSTDIN:') == 0)
	{
		@_ = splice(@_, length('QSetSTDIN:'));
		printf ("SetSTDIN (path ='%s')\n", get_hex_string (\@_));
	}
	elsif (index ($gen_query_cmd, 'QSetSTDOUT:') == 0)
	{
		@_ = splice(@_, length('QSetSTDOUT:'));
		printf ("SetSTDOUT (path ='%s')\n", get_hex_string (\@_));
	}
	elsif (index ($gen_query_cmd, 'QSetSTDERR:') == 0)
	{
		@_ = splice(@_, length('QSetSTDERR:'));
		printf ("SetSTDERR (path ='%s')\n", get_hex_string (\@_));
	}
	else
	{
		print $gen_query_cmd;
	}
	print "\n";
}

#----------------------------------------------------------------------
# 'k' command
#----------------------------------------------------------------------
sub dump_kill_cmd
{
	my $cmd = shift;
	print "kill (" . join('',@_) . ")\n";
}

#----------------------------------------------------------------------
# 'g' command
#----------------------------------------------------------------------
sub dump_read_regs_cmd
{
	my $cmd = shift;
	print "read_registers ()\n";
}

#----------------------------------------------------------------------
# 'G' command
#----------------------------------------------------------------------
sub dump_write_regs_cmd
{
	print "write_registers:\n";
	my $cmd = shift;
    foreach my $reg_href (@$registers_aref)
    {
		last if ($_[0] eq '#');
		if ($reg_href->{pseudo} == 0)
		{
            my $reg_info_href = $reg_href->{info};
            my $reg_name = $reg_href->{name};
            my $reg_extract = $reg_info_href->{extract};
            my $reg_format = $reg_info_href->{format};
            my $reg_val = &$reg_extract(\@_);
    		printf("\t%*s = $reg_format\n", $max_register_name_len, $reg_name, $reg_val);		    
		}
	}			
}

sub dump_read_regs_rsp
{
	print "read_registers () =>\n";
	if (!is_error_response(@_))
	{
	#	print join('',@_) . "\n";
	    foreach my $reg_href (@$registers_aref)
	    {
			last if ($_[0] eq '#');
    		if ($reg_href->{pseudo} == 0)
    		{
    	        my $reg_info_href = $reg_href->{info};
    	        my $reg_name = $reg_href->{name};
    	        my $reg_extract = $reg_info_href->{extract};
                my $reg_format = $reg_info_href->{format};
                my $reg_val = &$reg_extract(\@_);
    			printf("\t%*s = $reg_format\n", $max_register_name_len, $reg_name, $reg_val);
			}
		}			
	}
}

sub dump_read_single_register_rsp
{
    dump_register_value(0, \@_, $reg_cmd_reg);
    print "\n";
}

#----------------------------------------------------------------------
# '_M' - allocate memory command (LLDB extension)
#
#   Command: '_M'
#      Arg1: Hex byte size as big endian hex string
# Separator: ','
#      Arg2: permissions as string that must be a string that contains any
#            combination of 'r' (readable) 'w' (writable) or 'x' (executable)
#
#   Returns: The address that was allocated as a big endian hex string
#            on success, else an error "EXX" where XX are hex bytes
#            that indicate an error code.
#
# Examples:
#   _M10,rw     # allocate 16 bytes with read + write permissions
#   _M100,rx    # allocate 256 bytes with read + execute permissions
#----------------------------------------------------------------------
sub dump_allocate_memory_cmd
{
	shift; shift; # shift off the '_' and the 'M'
	my $byte_size = get_addr(\@_);
	shift;	# Skip ','
	printf("allocate_memory ( byte_size = %u (0x%x), permissions = %s)\n", $byte_size, $byte_size, join('',@_));
}

sub dump_allocate_memory_rsp
{
    if (@_ == 3 and $_[0] == 'E')
    {
	    printf("allocated memory addr = ERROR (%s))\n", join('',@_));        
    }
    else
    {
	    printf("allocated memory addr = 0x%s\n", join('',@_));        
    }
}

#----------------------------------------------------------------------
# '_m' - deallocate memory command (LLDB extension)
#
#   Command: '_m'
#      Arg1: Hex address as big endian hex string
#
#   Returns: "OK" on success "EXX" on error
#
# Examples:
#   _m201000    # Free previously allocated memory at address 0x201000
#----------------------------------------------------------------------
sub dump_deallocate_memory_cmd
{
	shift; shift; # shift off the '_' and the 'm'
	printf("deallocate_memory ( addr =  0x%s)\n", join('',@_));
}


#----------------------------------------------------------------------
# 'p' command
#----------------------------------------------------------------------
sub dump_read_single_register_cmd
{
	my $cmd = shift;
	$reg_cmd_reg = get_hex(\@_);
	my $thread = get_thread_from_thread_suffix (\@_);
	my $reg_href = $$registers_aref[$reg_cmd_reg];
  
	if (defined $thread)
	{
    	print "read_register ( reg = \"$reg_href->{name}\", thread = $thread )\n";
	}
	else
	{
    	print "read_register ( reg = \"$reg_href->{name}\" )\n";
	}
}


#----------------------------------------------------------------------
# 'P' command
#----------------------------------------------------------------------
sub dump_write_single_register_cmd
{
	my $cmd = shift;
	my $reg_num = get_hex(\@_);
	shift (@_);	# Discard the '='
	
	print "write_register ( ";
	dump_register_value(0, \@_, $reg_num);
	my $thread = get_thread_from_thread_suffix (\@_);
	if (defined $thread)
	{
	    print ", thread = $thread";
	}
	print " )\n";
}

#----------------------------------------------------------------------
# 'm' command
#----------------------------------------------------------------------
our $read_mem_address = 0;
sub dump_read_mem_cmd
{
	my $cmd = shift;
	$read_mem_address = get_addr(\@_);
	shift;	# Skip ','
	printf("read_mem ( $addr_format, %s )\n", $read_mem_address, join('',@_));
}

#----------------------------------------------------------------------
# 'm' response
#----------------------------------------------------------------------
sub dump_read_mem_rsp
{
	# If the memory read was 2 or 4 bytes, print it out in native format
	# instead of just as bytes.
	my $num_nibbles = @_;
	if ($num_nibbles == 2)
	{
		printf(" 0x%2.2x", get8(\@_));
	}
	elsif ($num_nibbles == 4)
	{
		printf(" 0x%4.4x", get16(\@_));
	}
	elsif ($num_nibbles == 8)
	{
		printf(" 0x%8.8x", get32(\@_));
	}
	elsif ($num_nibbles == 16)
	{
		printf(" 0x%s", get64(\@_));
	}
	else
	{
		my $curr_address = $read_mem_address;
		my $nibble;
		my $nibble_offset = 0;
		my $max_nibbles_per_line = 2 * $max_bytes_per_line;
		foreach $nibble (@_)
		{
			if (($nibble_offset % $max_nibbles_per_line) == 0)
			{
				($nibble_offset > 0) and print "\n    ";
				printf("$addr_format: ", $curr_address + $nibble_offset/2);
			}
			(($nibble_offset % 2) == 0) and print ' ';			
			print $nibble;
			$nibble_offset++;
		}
	}
	print "\n";
}

#----------------------------------------------------------------------
# 'c' or 's' command
#----------------------------------------------------------------------
sub dump_continue_cmd
{	
	my $cmd = shift;
	my $cmd_str;
	$cmd eq 'c' and $cmd_str = 'continue';
	$cmd eq 's' and $cmd_str = 'step';
	my $address = -1;
	if (@_)
	{
		my $address = get_addr(\@_);
		printf("%s ($addr_format)\n", $cmd_str, $address);
	}
	else
	{
		printf("%s ()\n", $cmd_str);
	}
}

#----------------------------------------------------------------------
# 'Css' continue (C) with signal (ss where 'ss' is two hex digits)
# 'Sss' step (S) with signal (ss where 'ss' is two hex digits)
#----------------------------------------------------------------------
sub dump_continue_with_signal_cmd
{	
	my $cmd = shift;
	my $address = -1;
	my $cmd_str;
	$cmd eq 'c' and $cmd_str = 'continue';
	$cmd eq 's' and $cmd_str = 'step';
	my $signal = get_hex(\@_);
	if (@_)
	{
		my $address = 0;
		if (@_ && $_[0] == ';')
		{
			shift;
		 	$address = get_addr(\@_);
		}
	}

	if ($address != -1)
	{
		printf("%s_with_signal (signal = 0x%2.2x, address = $addr_format)\n", $cmd_str, $signal, $address);
	}
	else
	{
		printf("%s_with_signal (signal = 0x%2.2x)\n", $cmd_str, $signal);
	}
}

#----------------------------------------------------------------------
# 'A' command
#----------------------------------------------------------------------
sub dump_A_command
{	
	my $cmd = get_expected_char(\@_, 'A') or print "error: incorrect command letter for argument packet, expected 'A'\n";
	printf("set_program_arguments (\n");
	do
	{
		my $arg_len = get_uint(\@_);
		get_expected_char(\@_, ',') or die "error: missing comma after argument length...?\n";
		my $arg_idx = get_uint(\@_);
		get_expected_char(\@_, ',') or die "error: missing comma after argument number...?\n";
	
		my $arg = '';
		my $num_hex8_bytes = $arg_len/2;
		for (1 .. $num_hex8_bytes)
		{
			$arg .= sprintf("%c", get8(\@_))
		}
		printf("        <%3u> argv[%u] = '%s'\n", $arg_len, $arg_idx, $arg);
		if (@_ > 0)
		{
			get_expected_char(\@_, ',') or die "error: missing comma after argument argument ASCII hex bytes...?\n";
		}		
	} while (@_ > 0);	
	printf("    )\n");
}


#----------------------------------------------------------------------
# 'z' and 'Z' command
#----------------------------------------------------------------------
sub dump_bp_wp_command
{	
	my $cmd = shift;
	my $type = shift;
	shift;	# Skip ','
	my $address = get_addr(\@_);
	shift;	# Skip ','
	my $length = join('',@_);
	if ($cmd eq 'z')
	{
		printf("remove $point_types[$type]($addr_format, %d)\n", $address, $length);
	}
	else
	{
		printf("insert $point_types[$type]($addr_format, %d)\n", $address, $length);		
	}
}


#----------------------------------------------------------------------
# 'X' command
#----------------------------------------------------------------------
sub dump_write_mem_binary_cmd
{	
	my $cmd = shift;
	my $address = get_addr(\@_);
	shift;	# Skip ','
	
	my ($length, $binary) = split(/:/, join('',@_));
	printf("write_mem_binary ( $addr_format, %d, %s)\n", $address, $length, $binary);

}

#----------------------------------------------------------------------
# 'M' command
#----------------------------------------------------------------------
sub dump_write_mem_cmd
{	
	my $cmd = shift;
	my $address = get_addr(\@_);
	shift;	# Skip ','
	my ($length, $hex_bytes) = split(/:/, join('',@_));
#	printf("write_mem ( $addr_format, %d, %s)\n", $address, $length, $hex_bytes);
	printf("write_mem ( addr = $addr_format, len = %d (0x%x), bytes = ", $address, $length, $length);
	splice(@_, 0, length($length)+1);

	my $curr_address = $address;
	my $nibble;
	my $nibble_count = 0;
	my $max_nibbles_per_line = 2 * $max_bytes_per_line;
	foreach $nibble (@_)
	{
		(($nibble_count % 2) == 0) and print ' ';
		print $nibble;
		$nibble_count++;
	}

	# If the memory to write is 2 or 4 bytes, print it out in native format
	# instead of just as bytes.
	if (@_ == 4)
	{
		printf(" ( 0x%4.4x )", get16(\@_));
	}
	elsif (@_ == 8)
	{
		printf(" ( 0x%8.8x )", get32(\@_));
	}
	print " )\n";

}

#----------------------------------------------------------------------
# 'v' command
#----------------------------------------------------------------------
our $extended_rsp_callback = 0;
sub dump_extended_cmd
{
	$extended_rsp_callback = 0;
	if (join('', @_[0..4]) eq "vCont")
	{
		dump_extended_continue_cmd(splice(@_,5));
	}
	elsif (join('', @_[0..7]) eq 'vAttach;')
	{
		dump_attach_command (splice(@_,8));
	}
	elsif (join('', @_[0..11]) eq 'vAttachWait;')
	{
		dump_attach_wait_command (splice(@_,12));
	}
}

#----------------------------------------------------------------------
# 'v' response
#----------------------------------------------------------------------
sub dump_extended_rsp
{
	if ($extended_rsp_callback)
	{
		&$extended_rsp_callback(@_);
	}
	$extended_rsp_callback = 0;
}

#----------------------------------------------------------------------
# 'vAttachWait' command
#----------------------------------------------------------------------
sub dump_attach_wait_command
{
	print "attach_wait ( ";
	while (@_)
	{
		printf("%c", get8(\@_))
	}
	printf " )\n";
	
}

#----------------------------------------------------------------------
# 'vAttach' command
#----------------------------------------------------------------------
sub dump_attach_command
{
	printf("attach ( pid = %i )", get_hex(\@_));
	$extended_rsp_callback = \&dump_stop_reply_packet;
}

#----------------------------------------------------------------------
# 'vCont' command
#----------------------------------------------------------------------
sub dump_extended_continue_cmd
{
	print "extended_continue ( ";
	my $cmd = shift;
	if ($cmd eq '?')
	{
		print "list supported modes )\n";
		$extended_rsp_callback = \&dump_extended_continue_rsp;
	}
	elsif  ($cmd eq ';')
	{
		$extended_rsp_callback = \&dump_stop_reply_packet;
		my $i = 0;
		while ($#_ >= 0)
		{
			if ($i > 0)
			{
				print ", ";
			}
			my $continue_cmd = shift;
			my $tmp;
			if ($continue_cmd eq 'c')
			{ 
				print "continue";
			}
			elsif ($continue_cmd eq 'C')			
			{
				print "continue with signal ";
				print shift;
				print shift;
			}
			elsif ($continue_cmd eq 's')			
			{ 
				print "step";
			}
			elsif ($continue_cmd eq 'S')			
			{
				print "step with signal ";
				print shift;
				print shift;
			}

			if ($_[0] eq ':')
			{
				shift; # Skip ':'
				print " for thread ";
				while ($#_ >= 0)
				{
					$tmp = shift;
					if (length($tmp) > 0 && $tmp ne ';') {
						print $tmp; 
					} else { 
						last;
					}
				}
			}
			$i++;
		}
		
		printf " )\n";
	}
}

#----------------------------------------------------------------------
# 'vCont' response
#----------------------------------------------------------------------
sub dump_extended_continue_rsp
{
	if (scalar(@_) == 0)
	{
		print "$unimplemented_str\n";
	}
	else
	{
		print "extended_continue supports " . join('',@_) . "\n";
	}
}

#----------------------------------------------------------------------
# Dump the command ascii for any unknown commands
#----------------------------------------------------------------------
sub dump_other_cmd
{
	print "other = " . join('',@_) . "\n";
}

#----------------------------------------------------------------------
# Check to see if the response was unsupported with appropriate checksum
#----------------------------------------------------------------------
sub rsp_is_unsupported
{
	return join('',@_) eq "#00";
}

#----------------------------------------------------------------------
# Check to see if the response was "OK" with appropriate checksum
#----------------------------------------------------------------------
sub rsp_is_OK
{
	return join('',@_) eq "OK#9a";
}

#----------------------------------------------------------------------
# Dump a response for an unknown command
#----------------------------------------------------------------------
sub dump_other_rsp
{
	print "other = " . join('',@_) . "\n";
}

#----------------------------------------------------------------------
# Get a byte from the ascii string assuming that the 2 nibble ascii
# characters are in hex. 
#
# The argument for this function needs to be a reference to an array 
# that contains single character strings and the array will get 
# updated by shifting characters off the front of it (no leading # "0x")
#----------------------------------------------------------------------
sub get8
{
	my $arrayref = shift;
	my $val = hex(shift(@$arrayref) . shift(@$arrayref));
	return $val;
}

#----------------------------------------------------------------------
# Get a 16 bit integer and swap if $swap global is set to a non-zero 
# value.
#
# The argument for this function needs to be a reference to an array 
# that contains single character strings and the array will get 
# updated by shifting characters off the front of it (no leading # "0x")
#----------------------------------------------------------------------
sub get16
{
	my $arrayref = shift;
	my $val = 0;
	if ($swap)
	{
		$val =	get8($arrayref) 	|
				get8($arrayref) << 8;
	}
	else
	{
		$val =	get8($arrayref) << 8 |
				get8($arrayref)		 ;
	}
	return $val;
}

#----------------------------------------------------------------------
# Get a 32 bit integer and swap if $swap global is set to a non-zero 
# value.
#
# The argument for this function needs to be a reference to an array 
# that contains single character strings and the array will get 
# updated by shifting characters off the front of it (no leading # "0x")
#----------------------------------------------------------------------
sub get32
{
	my $arrayref = shift;
	my $val = 0;
	if ($swap)
	{
		$val =	get8($arrayref)       |
				get8($arrayref) << 8  |
				get8($arrayref) << 16 |
				get8($arrayref) << 24 ;
	}
	else
	{
		$val =	get8($arrayref) << 24 |
				get8($arrayref) << 16 |
				get8($arrayref) <<  8 |
				get8($arrayref)       ;
	}
	return $val;
}

#----------------------------------------------------------------------
# Get a 64 bit hex value as a string
#
# The argument for this function needs to be a reference to an array 
# that contains single character strings and the array will get 
# updated by shifting characters off the front of it (no leading # "0x")
#----------------------------------------------------------------------
sub get64
{
	my $arrayref = shift;
	my $val = '';
	my @nibbles;
	if ($swap)
	{
        push @nibbles, splice(@$arrayref, 14, 2);
        push @nibbles, splice(@$arrayref, 12, 2);
        push @nibbles, splice(@$arrayref, 10, 2);
        push @nibbles, splice(@$arrayref, 8, 2);
        push @nibbles, splice(@$arrayref, 6, 2);
        push @nibbles, splice(@$arrayref, 4, 2);
        push @nibbles, splice(@$arrayref, 2, 2);
        push @nibbles, splice(@$arrayref, 0, 2);
	}
	else
	{
	    (@nibbles) = splice(@$arrayref, 0, ((64/8) * 2));
	}
    $val = join('', @nibbles);        
	return $val;
}

#----------------------------------------------------------------------
# Get a 80 bit hex value as a string
#
# The argument for this function needs to be a reference to an array 
# that contains single character strings and the array will get 
# updated by shifting characters off the front of it (no leading # "0x")
#----------------------------------------------------------------------
sub get80
{
	my $arrayref = shift;
	my $val = '';
	my @nibbles;
	if ($swap)
	{
        push @nibbles, splice(@$arrayref, 18, 2);
        push @nibbles, splice(@$arrayref, 16, 2);
        push @nibbles, splice(@$arrayref, 14, 2);
        push @nibbles, splice(@$arrayref, 12, 2);
        push @nibbles, splice(@$arrayref, 10, 2);
        push @nibbles, splice(@$arrayref, 8, 2);
        push @nibbles, splice(@$arrayref, 6, 2);
        push @nibbles, splice(@$arrayref, 4, 2);
        push @nibbles, splice(@$arrayref, 2, 2);
        push @nibbles, splice(@$arrayref, 0, 2);
	}
	else
	{
	    (@nibbles) = splice(@$arrayref, 0, ((80/8) * 2));
	}
    $val = join('', @nibbles);        
	return $val;
}

#----------------------------------------------------------------------
# Get a 96 bit hex value as a string
#
# The argument for this function needs to be a reference to an array 
# that contains single character strings and the array will get 
# updated by shifting characters off the front of it (no leading # "0x")
#----------------------------------------------------------------------
sub get96
{
	my $arrayref = shift;
	my $val = '';
	my @nibbles;
	if ($swap)
	{
        push @nibbles, splice(@$arrayref, 22, 2);
        push @nibbles, splice(@$arrayref, 20, 2);
        push @nibbles, splice(@$arrayref, 18, 2);
        push @nibbles, splice(@$arrayref, 16, 2);
        push @nibbles, splice(@$arrayref, 14, 2);
        push @nibbles, splice(@$arrayref, 12, 2);
        push @nibbles, splice(@$arrayref, 10, 2);
        push @nibbles, splice(@$arrayref, 8, 2);
        push @nibbles, splice(@$arrayref, 6, 2);
        push @nibbles, splice(@$arrayref, 4, 2);
        push @nibbles, splice(@$arrayref, 2, 2);
        push @nibbles, splice(@$arrayref, 0, 2);
	}
	else
	{
	    (@nibbles) = splice(@$arrayref, 0, ((96/8) * 2));
	}
    $val = join('', @nibbles);        
	return $val;
}

#----------------------------------------------------------------------
# Get a 128 bit hex value as a string
#
# The argument for this function needs to be a reference to an array 
# that contains single character strings and the array will get 
# updated by shifting characters off the front of it (no leading # "0x")
#----------------------------------------------------------------------
sub get128
{
	my $arrayref = shift;
	my $val = '';
	my @nibbles;
	if ($swap)
	{
        push @nibbles, splice(@$arrayref, 30, 2);
        push @nibbles, splice(@$arrayref, 28, 2);
        push @nibbles, splice(@$arrayref, 26, 2);
        push @nibbles, splice(@$arrayref, 24, 2);
        push @nibbles, splice(@$arrayref, 22, 2);
        push @nibbles, splice(@$arrayref, 20, 2);
        push @nibbles, splice(@$arrayref, 18, 2);
        push @nibbles, splice(@$arrayref, 16, 2);
        push @nibbles, splice(@$arrayref, 14, 2);
        push @nibbles, splice(@$arrayref, 12, 2);
        push @nibbles, splice(@$arrayref, 10, 2);
        push @nibbles, splice(@$arrayref, 8, 2);
        push @nibbles, splice(@$arrayref, 6, 2);
        push @nibbles, splice(@$arrayref, 4, 2);
        push @nibbles, splice(@$arrayref, 2, 2);
        push @nibbles, splice(@$arrayref, 0, 2);
	}
	else
	{
	    (@nibbles) = splice(@$arrayref, 0, ((128/8) * 2));
	}
    $val = join('', @nibbles);        
	return $val;
}

#----------------------------------------------------------------------
# Get a 256 bit hex value as a string
#
# The argument for this function needs to be a reference to an array 
# that contains single character strings and the array will get 
# updated by shifting characters off the front of it (no leading # "0x")
#----------------------------------------------------------------------
sub get256
{
	my $arrayref = shift;
	my $val = '';
	my @nibbles;
	if ($swap)
	{
        push @nibbles, splice(@$arrayref, 62, 2);
        push @nibbles, splice(@$arrayref, 60, 2);
        push @nibbles, splice(@$arrayref, 58, 2);
        push @nibbles, splice(@$arrayref, 56, 2);
        push @nibbles, splice(@$arrayref, 54, 2);
        push @nibbles, splice(@$arrayref, 52, 2);
        push @nibbles, splice(@$arrayref, 50, 2);
        push @nibbles, splice(@$arrayref, 48, 2);
        push @nibbles, splice(@$arrayref, 46, 2);
        push @nibbles, splice(@$arrayref, 44, 2);
        push @nibbles, splice(@$arrayref, 42, 2);
        push @nibbles, splice(@$arrayref, 40, 2);
        push @nibbles, splice(@$arrayref, 38, 2);
        push @nibbles, splice(@$arrayref, 36, 2);
        push @nibbles, splice(@$arrayref, 34, 2);
        push @nibbles, splice(@$arrayref, 32, 2);
        push @nibbles, splice(@$arrayref, 30, 2);
        push @nibbles, splice(@$arrayref, 28, 2);
        push @nibbles, splice(@$arrayref, 26, 2);
        push @nibbles, splice(@$arrayref, 24, 2);
        push @nibbles, splice(@$arrayref, 22, 2);
        push @nibbles, splice(@$arrayref, 20, 2);
        push @nibbles, splice(@$arrayref, 18, 2);
        push @nibbles, splice(@$arrayref, 16, 2);
        push @nibbles, splice(@$arrayref, 14, 2);
        push @nibbles, splice(@$arrayref, 12, 2);
        push @nibbles, splice(@$arrayref, 10, 2);
        push @nibbles, splice(@$arrayref, 8, 2);
        push @nibbles, splice(@$arrayref, 6, 2);
        push @nibbles, splice(@$arrayref, 4, 2);
        push @nibbles, splice(@$arrayref, 2, 2);
        push @nibbles, splice(@$arrayref, 0, 2);
	}
	else
	{
	    (@nibbles) = splice(@$arrayref, 0, ((256/8) * 2));
	}
    $val = join('', @nibbles);        
	return $val;
}

#----------------------------------------------------------------------
# Get an unsigned integer value by grabbing items off the front of 
# the array stopping when a non-digit char string is encountered.
#
# The argument for this function needs to be a reference to an array 
# that contains single character strings and the array will get 
# updated by shifting characters off the front of it
#----------------------------------------------------------------------
sub get_uint
{
	my $arrayref = shift;
	@$arrayref == 0 and return 0;
	my $val = 0;
	while ($$arrayref[0] =~ /[0-9]/)
	{
		$val = $val * 10 + int(shift(@$arrayref));
	}
	return $val;
}

#----------------------------------------------------------------------
# Check the first character in the array and if it matches the expected
# character, return that character, else return undef;
#
# The argument for this function needs to be a reference to an array 
# that contains single character strings and the array will get 
# updated by shifting characters off the front of it. If the expected
# character doesn't match, it won't touch the array. If the first
# character does match, it will shift it off and return it.
#----------------------------------------------------------------------
sub get_expected_char
{
	my $arrayref = shift;
	my $expected_char = shift;
	if ($expected_char eq $$arrayref[0])
	{
		return shift(@$arrayref);
	}
	return undef;
}
#----------------------------------------------------------------------
# Get a hex value by grabbing items off the front of the array and 
# stopping when a non-hex char string is encountered.
#
# The argument for this function needs to be a reference to an array 
# that contains single character strings and the array will get 
# updated by shifting characters off the front of it (no leading # "0x")
#----------------------------------------------------------------------
sub get_hex
{
	my $arrayref = shift;
	my $my_swap = @_ ? shift : 0;
	my $shift = 0;
	my $val = 0;
	while ($$arrayref[0] =~ /[0-9a-fA-F]/)
	{
		if ($my_swap)
		{
			my $byte = hex(shift(@$arrayref)) << 4 | hex(shift(@$arrayref));
			$val |= $byte << $shift;
			$shift += 8;
		}
		else
		{
			$val <<= 4;
			$val |= hex(shift(@$arrayref));
		}
	}
	return $val;
}

#----------------------------------------------------------------------
# Get an address value by grabbing items off the front of the array.
#
# The argument for this function needs to be a reference to an array 
# that contains single character strings and the array will get 
# updated by shifting characters off the front of it (no leading # "0x")
#----------------------------------------------------------------------
sub get_addr
{
	get_hex(shift);
}

sub get_hex_string
{
	my $arrayref = shift;
	my $str = '';
	while ($$arrayref[0] =~ /[0-9a-fA-F]/ and $$arrayref[1] =~ /[0-9a-fA-F]/)
	{
		my $hi_nibble = hex(shift(@$arrayref));
		my $lo_nibble = hex(shift(@$arrayref));
		my $byte = ($hi_nibble << 4) | $lo_nibble;
		$str .= chr($byte);
	}
	return $str;
}

sub dump_stop_reply_data
{
    while ($#_ >= 0)
	{
		last unless ($_[0] ne '#');
		
	
		my $key = '';
		my $value = '';
		my $comment = '';
        if ($_[0] =~ /[0-9a-fA-F]/ && $_[1] =~ /[0-9a-fA-F]/)
    	{
    		my $reg_num = get8(\@_);
    		shift(@_);	# Skip ':'
    		if (defined ($registers_aref) && $reg_num < @$registers_aref)
    		{
                dump_register_value(1, \@_, $reg_num);
                print "\n";
        		shift(@_);	# Skip ';'
        		next;
    		}
    		$key = sprintf("reg %u", $reg_num);
    	}
    	my $char;
    	
    	if (length($key) == 0)
    	{
    		while (1)
    		{
    			$char = shift(@_);
    			if (length($char) == 0 or $char eq ':' or $char eq '#') { last; }
    			$key .= $char;
    		}
    	}
    	
		while (1)
		{
			$char = shift(@_);
			if (length($char) == 0 or $char eq ';' or $char eq '#') { last; }
			$value .= $char;
		}
		if ($key eq 'metype')
		{
		    our %metype_to_name = (
		        '1' => ' (EXC_BAD_ACCESS)',
                '2' => ' (EXC_BAD_INSTRUCTION)',
                '3' => ' (EXC_ARITHMETIC)',
                '4' => ' (EXC_EMULATION)',
                '5' => ' (EXC_SOFTWARE)',
                '6' => ' (EXC_BREAKPOINT)',
                '7' => ' (EXC_SYSCALL)',
                '8' => ' (EXC_MACH_SYSCALL)',
                '9' => ' (EXC_RPC_ALERT)',
                '10' => ' (EXC_CRASH)'
            );
            if (exists $metype_to_name{$value})
            {
                $comment = $metype_to_name{$value};
            }
		}
		printf("\t%*s = %s$comment\n", $max_register_name_len, $key, $value);
	}
}

#----------------------------------------------------------------------
# Dumps a Stop Reply Packet which happens in response to a step, 
# continue, last signal, and probably a few other commands.
#----------------------------------------------------------------------
sub dump_stop_reply_packet
{
	my $what = shift(@_);
	if ($what eq 'S' or $what eq 'T')
	{
	    my $signo = get8(\@_);
	    
	    our %signo_to_name = (
                '1'  => ' SIGHUP',
                '2'  => ' SIGINT',
                '3'  => ' SIGQUIT',
                '4'  => ' SIGILL',
                '5'  => ' SIGTRAP',
                '6'  => ' SIGABRT',
                '7'  => ' SIGPOLL/SIGEMT',
                '8'  => ' SIGFPE',
                '9'  => ' SIGKILL',
                '10' => ' SIGBUS',
                '11' => ' SIGSEGV',
                '12' => ' SIGSYS',
                '13' => ' SIGPIPE',
                '14' => ' SIGALRM',
                '15' => ' SIGTERM',
                '16' => ' SIGURG',
                '17' => ' SIGSTOP',
                '18' => ' SIGTSTP',
                '19' => ' SIGCONT',
                '20' => ' SIGCHLD',
                '21' => ' SIGTTIN',
                '22' => ' SIGTTOU',
                '23' => ' SIGIO',
                '24' => ' SIGXCPU',
                '25' => ' SIGXFSZ',
                '26' => ' SIGVTALRM',
                '27' => ' SIGPROF',
                '28' => ' SIGWINCH',
                '29' => ' SIGINFO',
                '30' => ' SIGUSR1',
                '31' => ' SIGUSR2',
                '145' => ' TARGET_EXC_BAD_ACCESS',        # 0x91
                '146' => ' TARGET_EXC_BAD_INSTRUCTION',   # 0x92
                '147' => ' TARGET_EXC_ARITHMETIC',        # 0x93
                '148' => ' TARGET_EXC_EMULATION',         # 0x94
                '149' => ' TARGET_EXC_SOFTWARE',          # 0x95
                '150' => ' TARGET_EXC_BREAKPOINT'         # 0x96
        );
        my $signo_str = sprintf("%i", $signo);
        my $signo_name = '';
	    if (exists $signo_to_name{$signo_str})
        {
            $signo_name = $signo_to_name{$signo_str};
        }
		printf ("signal (signo=%u$signo_name)\n", $signo);
		dump_stop_reply_data (@_);
	}
	elsif ($what eq 'W')
	{
		print 'process_exited( ' . shift(@_) . shift(@_) . " )\n";
	}
	elsif ($what eq 'X')
	{
		print 'process_terminated( ' . shift(@_) . shift(@_) . " )\n";
	}
	elsif ($what eq 'O')
	{
		my $console_output = '';
		my $num_hex8_bytes = @_/2;
		for (1 .. $num_hex8_bytes)
		{
			$console_output .= sprintf("%c", get8(\@_))
		}
		
		print "program_console_output('$console_output')\n";
	}
}

#----------------------------------------------------------------------
# '?' command
#----------------------------------------------------------------------
sub dump_last_signal_cmd
{
	my $cmd = shift;
	print 'last_signal (' . join('',@_) . ")\n";
}

sub dump_raw_command
{
	my $cmd_aref = shift;
	my $callback_ref;
	$curr_cmd = $$cmd_aref[0];

    if ($curr_cmd eq 'q' or $curr_cmd eq 'Q' or $curr_cmd eq '_')
    {
        $curr_full_cmd = '';
        foreach my $ch (@$cmd_aref)
        {
            $ch !~ /[A-Za-z_]/ and last;
            $curr_full_cmd .= $ch;
        }
    }
    else
    {
        $curr_full_cmd = $curr_cmd;
    }
	
	$curr_cmd eq '_' and $curr_cmd .= $$cmd_aref[1];	
	$callback_ref = $cmd_callbacks{$curr_cmd};
	if ($callback_ref)
	{
		&$callback_ref(@$cmd_aref);
	}
	else
	{
		# Strip the command byte for responses since we injected that above
		dump_other_cmd(@$cmd_aref); 
	} 		
}

sub dump_standard_response
{
	my $cmd_aref = shift;
	
	my $cmd_len = scalar(@$cmd_aref);
	if ($cmd_len == 0)
	{
		print "$unimplemented_str\n";
		return 1;
	}	

	my $response = join('', @$cmd_aref);
	if ($response eq 'OK')
	{
		print "$success_str\n";
		return 1;
	}
	
	if ($cmd_len == 3 and index($response, 'E') == 0)
	{
		print "ERROR: " . substr($response, 1) . "\n";
		return 1;		
	}
	
	return 0;
}
sub dump_raw_response
{
	my $cmd_aref = shift;
	my $callback_ref;
	
	if ($packet_start_time != 0.0)
	{
	    if (length($curr_full_cmd) > 0)
	    {
            $packet_times{$curr_full_cmd} += $curr_time - $packet_start_time;
	    }
	    else
	    {
            $packet_times{$curr_cmd} += $curr_time - $packet_start_time;
	    }
        $packet_start_time = 0.0;
	}
	
	$callback_ref = $rsp_callbacks{$curr_cmd};

	if ($callback_ref)
	{
		&$callback_ref(@$cmd_aref);
	}
	else
	{
		dump_standard_response($cmd_aref) or dump_other_rsp(@$cmd_aref);
	} 	
	
}
#----------------------------------------------------------------------
# Dumps any command and handles simple error checking on the responses
# for commands that are unsupported or OK.
#----------------------------------------------------------------------
sub dump_command
{
	my $cmd_str = shift;

	# Dump the original command string if verbose is on
	if ($opt_v)
	{
		print "dump_command($cmd_str)\n    ";
	}

	my @cmd_chars = extract_command($cmd_str);
	my $is_cmd = 1;
	
	my $cmd = $cmd_chars[0];
	if ($cmd eq '$')
	{
		$is_cmd = 0;		# Note that this is a reply
		$cmd = $curr_cmd;	# set the command byte appropriately
		shift @cmd_chars;	# remove the '$' from the cmd bytes
	}
	
	# Check for common responses across all commands and handle them
	# if we can
	if ( $is_cmd == 0 )
	{
		if (rsp_is_unsupported(@cmd_chars))
		{
			print "$unimplemented_str\n";
			return;
		}
		elsif (rsp_is_OK(@cmd_chars))
		{
			print "$success_str\n";
			return;
		}
		# Strip the checksum information for responses
		strip_checksum(\@cmd_chars);
	}

	my $callback_ref;
	if ($is_cmd) {
		$callback_ref = $cmd_callbacks{$cmd};
	} else {
		$callback_ref = $rsp_callbacks{$cmd};
	}

	if ($callback_ref)
	{
		&$callback_ref(@cmd_chars);
	}
	else
	{
		# Strip the command byte for responses since we injected that above
		if ($is_cmd) {
			dump_other_cmd(@cmd_chars); 
		} else {
			dump_other_rsp(@cmd_chars);
		}
		
	} 	
}


#----------------------------------------------------------------------
# Process a gdbserver log line by looking for getpkt and putkpt and
# tossing any other lines.

#----------------------------------------------------------------------
sub process_log_line
{
	my $line = shift;
	#($opt_v and $opt_g) and print "# $line";

	my $extract_cmd = 0;
	my $delta_time = 0.0;
	if ($line =~ /^(\s*)([1-9][0-9]+\.[0-9]+)([^0-9].*)$/)
	{
	    my $leading_space = $1;
	    $curr_time = $2;
	    $line = $3;
	    if ($base_time == 0.0)
	    {
	        $base_time = $curr_time;
	    }
	    else
	    {
	        $delta_time = $curr_time - $last_time;
	    }
	    printf ("(%.6f, %+.6f): ",  $curr_time - $base_time, $delta_time);
	    $last_time = $curr_time;
	}
	else
	{
	    $curr_time = 0.0
	}

	if ($line =~ /getpkt /)
	{
		$extract_cmd = 1;
		print "\n--> ";
		$packet_start_time = $curr_time;
	}
	elsif ($line =~ /putpkt /)
	{
		$extract_cmd = 1;
		print "<-- ";
	}
	elsif ($line =~ /.*Sent:  \[[0-9]+\.[0-9]+[:0-9]*\] (.*)/)
	{
		$opt_g and print "maintenance dump-packets command: $1\n";
		my @raw_cmd_bytes = split(/ */, $1);
		$packet_start_time = $curr_time;
		print "\n--> ";
		dump_raw_command(\@raw_cmd_bytes);
		process_log_line($2);
	}
	elsif ($line =~ /.*Recvd: \[[0-9]+\.[0-9]+[:0-9]*\] (.*)/)
	{
		$opt_g and print "maintenance dump-packets reply: $1\n";
		my @raw_rsp_bytes = split(/ */, $1);
		print "<-- ";
		dump_raw_response(\@raw_rsp_bytes);		
		print "\n";
	}
	elsif ($line =~ /getpkt: (.*)/)
	{
		if ($1 =~ /\$([^#]+)#[0-9a-fA-F]{2}/)
		{
			$opt_g and print "command: $1\n";
			my @raw_cmd_bytes = split(/ */, $1);
			print "--> ";
    		$packet_start_time = $curr_time;
			dump_raw_command(\@raw_cmd_bytes);			
		}
		elsif ($1 =~ /\+/)
		{
			#print "--> ACK\n";
		}
		elsif ($1 =~ /-/)
		{
			#print "--> NACK\n";
		}
	}
	elsif ($line =~ /putpkt: (.*)/)
	{
		if ($1 =~ /\$([^#]+)#[0-9a-fA-F]{2}/)
		{
			$opt_g and print "response: $1\n";
			my @raw_rsp_bytes = split(/ */, $1);
			print "<-- ";
			dump_raw_response(\@raw_rsp_bytes);		
			print "\n";
		}
		elsif ($1 =~ /\+/)
		{
			#print "<-- ACK\n";
		}
		elsif ($1 =~ /-/)
		{
			#print "<-- NACK\n";
		}
	}
	elsif ($line =~ /send packet: (.*)/)
	{
		if ($1 =~ /\$([^#]+)#[0-9a-fA-F]{2}/)
		{
			$opt_g and print "command: $1\n";
			my @raw_cmd_bytes = split(/ */, $1);
			print "--> ";
    		$packet_start_time = $curr_time;
			dump_raw_command(\@raw_cmd_bytes);			
		}
		elsif ($1 =~ /\+/)
		{
			#print "--> ACK\n";
		}
		elsif ($1 =~ /-/)
		{
			#print "--> NACK\n";
		}
	}
	elsif ($line =~ /read packet: (.*)/)
	{
		if ($1 =~ /\$([^#]*)#[0-9a-fA-F]{2}/)
		{
			$opt_g and print "response: $1\n";
			my @raw_rsp_bytes = split(/ */, $1);
			print "<-- ";
			dump_raw_response(\@raw_rsp_bytes);		
			print "\n";
		}
		elsif ($1 =~ /\+/)
		{
			#print "<-- ACK\n";
		}
		elsif ($1 =~ /-/)
		{
			#print "<-- NACK\n";
		}
	}
	elsif ($line =~ /Sending packet: \$([^#]+)#[0-9a-fA-F]{2}\.\.\.(.*)/)
	{
		$opt_g and print "command: $1\n";
		my @raw_cmd_bytes = split(/ */, $1);
		print "\n--> ";
		$packet_start_time = $curr_time;
		dump_raw_command(\@raw_cmd_bytes);
		process_log_line($2);
	}
	elsif ($line =~ /Packet received: (.*)/)
	{
		$opt_g and print "response: $1\n";
		my @raw_rsp_bytes = split(/ */, $1);
		print "<-- ";
		dump_raw_response(\@raw_rsp_bytes);		
		print "\n";
	}
	
	if ($extract_cmd)
	{
		my $beg = index($line, '("') + 2;
		my $end = rindex($line, '");');
		$packet_start_time = $curr_time;
		dump_command(substr($line, $beg, $end - $beg));
	}
}


our $line_num = 0;
while(<>)
{
	$line_num++;
	$opt_q or printf("# %5d: $_", $line_num);
	process_log_line($_);
}

if (%packet_times)
{
    print "----------------------------------------------------------------------\n";
    print "Packet timing summary:\n";
    print "----------------------------------------------------------------------\n";
    print "Packet                 Time       %\n";
    print "---------------------- -------- ------\n";
    my @packet_names = keys %packet_times;
    my $total_packet_times = 0.0;
    foreach my $key (@packet_names)
    {
        $total_packet_times += $packet_times{$key};
    }

    foreach my $value (sort {$packet_times{$b} cmp $packet_times{$a}} @packet_names)
    {
        my $percent = ($packet_times{$value} / $total_packet_times) * 100.0;
        if ($percent < 10.0)
        {
            printf("%22s %1.6f   %2.2f\n", $value, $packet_times{$value}, $percent);
            
        }
        else
        {
            printf("%22s %1.6f  %2.2f\n", $value, $packet_times{$value}, $percent);            
        }
    }   
    print "---------------------- -------- ------\n";
    printf ("                 Total %1.6f 100.00\n", $total_packet_times);
}







