Inline stack frames with IDebugControl5.GetContextStackTraceEx? - windbg

How can I resolve the symbols of a DEBUG_STACK_FRAME_EX with inline frames?
I am aware of DEBUG_STACK_FRAME_EX and its ULONG InlineFrameContext, but I don't understand how to resolve the InlineFrameContext to the corresponding symbol.
And what is the FrameSignature and FrameId in INLINE_FRAME_CONTEXT?

InLineFrameContext is an union only FrameType is Defined which is the second byte in the ContextValue DWORD
this normally would be 25ed0200 for an inline frame (most of the times i noticed though it can be b5b80200 too in some system dll's inline frames
the output is same as kvf and .inline 1 (.inline enable )
if you disable inline
the InlineFrameContext would be 0xffffffff
where ff is STACK_FRAME_TYPE_IGNORE
25ed / b5b8 , ffff , etc The Frame Signature and the 00 FrameID
are fetched from symbols
(if you really want to know look into the dia sdk com sorcery which loads the symbols and fetches this signature from pdb ( dbghelp!g+0xxxx (2360 in win10 dbghelp.dll) )
whatever they are they don't appear to be useful for any co-relation with symbols and the signature varies on each invocation or windbg session
and iirc on every .reload /f
even the OutputContextStackTraceEx function doesn't seem to use the signature and Id (type is used to denote inline if type was 02 )
if you want to experiment you can check this code (its an engextcpp extension so you compile and load it in windbg and run the command it will
print the stack along with some spew exactly as if you issued kvf
#include <codeanalysis\warnings.h>
#pragma warning( push )
#pragma warning ( disable : ALL_CODE_ANALYSIS_WARNINGS )
#include <engextcpp.cpp>
#pragma warning( pop )
class EXT_CLASS : public ExtExtension {
public:
EXT_COMMAND_METHOD(gcsex);
};
EXT_DECLARE_GLOBALS();
ULONG64 InstructionOffset; DEBUG_STACK_FRAME ScopeFrame; CONTEXT ScopeContext;
DEBUG_STACK_FRAME_EX FrameEx[0x20];CONTEXT FrameContext[0x20];ULONG FramesFilled;
INLINE_FRAME_CONTEXT Inlineframectx;
EXT_COMMAND( gcsex, "", "" ) {
InstructionOffset = NULL; FramesFilled = NULL;
memset(&ScopeFrame,0,sizeof(ScopeFrame));memset(&ScopeContext,0,sizeof(ScopeContext));
memset(&FrameEx, 0, sizeof(FrameEx)); memset(&FrameContext,0,sizeof(FrameContext));
memset(&Inlineframectx,0,sizeof(Inlineframectx));
m_Symbols->GetScope(&InstructionOffset,&ScopeFrame,&ScopeContext,sizeof(ScopeContext));
Out("%I64X%I64x %X\n",InstructionOffset,ScopeFrame.InstructionOffset,ScopeContext.Eip);
m_Control5->GetContextStackTraceEx( &ScopeContext,sizeof(ScopeContext),
FrameEx,0x20,FrameContext,0x20*sizeof(CONTEXT),sizeof(CONTEXT),&FramesFilled );
Out("Frames Filled = %x\n" , FramesFilled);
for(ULONG i = 0 ; i < FramesFilled; i++) {
Inlineframectx.ContextValue = FrameEx[i].InlineFrameContext;
Out("Inline Frame Context for frame %d=%x\n" , i , FrameEx[i].InlineFrameContext);
Out("Frameid = %x FrameType = %x Frame Signature = %x\n" ,
Inlineframectx.FrameId , Inlineframectx.FrameType , Inlineframectx.FrameSignature);
}
m_Control5->OutputContextStackTraceEx ( DEBUG_OUTCTL_ALL_CLIENTS,FrameEx,FramesFilled,
&FrameContext,FramesFilled*sizeof(CONTEXT),sizeof(CONTEXT),0x1fff );
}
you should see some thing like this on executing the extension
0:000> kb
# ChildEBP RetAddr Args to Child
00 (Inline) -------- -------- -------- -------- runasm!helper [e:\test\runasm\runasm.cpp # 4]
01 0029fd44 0124159a 00000001 0008c5f0 00091a70 runasm!main+0x20 [e:\test\runasm\runasm.cpp # 32]
02 (Inline) -------- -------- -------- -------- runasm!invoke_main+0x1d [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl # 74]
03 0029fd90 75d53c45 7ffdf000 0029fddc 778037f5 runasm!__scrt_common_main_seh+0xff [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl # 264]
04 0029fd9c 778037f5 7ffdf000 75826e8f 00000000 kernel32!BaseThreadInitThunk+0xe
05 0029fddc 778037c8 01241652 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x70
06 0029fdf4 00000000 01241652 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b
0:000> !gcsex
12412F0 12412f0 12412F0
Frames Filled = 7
Inline Frame Context = for frame 0 = 25ed0200
Frameid = 0 FrameType = 2 Frame Signature = 25ed
Inline Frame Context = for frame 1 = 25ed0100
Frameid = 0 FrameType = 1 Frame Signature = 25ed
Inline Frame Context = for frame 2 = 25ed8200
Frameid = 0 FrameType = 82 Frame Signature = 25ed
Inline Frame Context = for frame 3 = 25ed8100
Frameid = 0 FrameType = 81 Frame Signature = 25ed
Inline Frame Context = for frame 4 = 25ed8100
Frameid = 0 FrameType = 81 Frame Signature = 25ed
Inline Frame Context = for frame 5 = 25ed8100
Frameid = 0 FrameType = 81 Frame Signature = 25ed
Inline Frame Context = for frame 6 = 25ed8100
Frameid = 0 FrameType = 81 Frame Signature = 25ed
# Memory ChildEBP RetAddr Args to Child
00 (Inline) -------- -------- -------- -------- runasm!helper (Inline Function # 012412f0) (CONV: cdecl) [e:\test\runasm\runasm.cpp # 4]
01 0029fd44 0124159a 00000001 0008c5f0 00091a70 runasm!main(void)+0x20 (FPO: [0,1,4]) (CONV: cdecl) [e:\test\runasm\runasm.cpp # 32]
02 4c (Inline) -------- -------- -------- -------- runasm!invoke_main+0x1d (Inline Function # 0124159a) (CONV: cdecl) [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl # 74]
03 0 0029fd90 75d53c45 7ffdf000 0029fddc 778037f5 runasm!__scrt_common_main_seh(void)+0xff (FPO: [Non-Fpo]) (CONV: cdecl) [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl # 264]
04 c 0029fd9c 778037f5 7ffdf000 75826e8f 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
05 40 0029fddc 778037c8 01241652 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
06 18 0029fdf4 00000000 01241652 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
0:000> .inline
Include inline function queries is "enabled".
0:000> .inline 0
Include inline function queries is "disabled".
0:000> kb
# ChildEBP RetAddr Args to Child
00 0029fd44 0124159a 00000001 0008c5f0 00091a70 runasm!main+0x20 [e:\test\runasm\runasm.cpp # 32]
01 0029fd90 75d53c45 7ffdf000 0029fddc 778037f5 runasm!__scrt_common_main_seh+0xff [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl # 264]
02 0029fd9c 778037f5 7ffdf000 75826e8f 00000000 kernel32!BaseThreadInitThunk+0xe
03 0029fddc 778037c8 01241652 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x70
04 0029fdf4 00000000 01241652 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b
0:000> !gcsex
12412F0 12412f0 12412F0
Frames Filled = 5
Inline Frame Context = for frame 0 = ffffffff
Frameid = ff FrameType = ff Frame Signature = ffff
Inline Frame Context = for frame 1 = ffffffff
Frameid = ff FrameType = ff Frame Signature = ffff
Inline Frame Context = for frame 2 = ffffffff
Frameid = ff FrameType = ff Frame Signature = ffff
Inline Frame Context = for frame 3 = ffffffff
Frameid = ff FrameType = ff Frame Signature = ffff
Inline Frame Context = for frame 4 = ffffffff
Frameid = ff FrameType = ff Frame Signature = ffff
# Memory ChildEBP RetAddr Args to Child
00 0029fd44 0124159a 00000001 0008c5f0 00091a70 runasm!main(void)+0x20 (FPO: [0,1,4]) (CONV: cdecl) [e:\test\runasm\runasm.cpp # 32]
01 4c 0029fd90 75d53c45 7ffdf000 0029fddc 778037f5 runasm!__scrt_common_main_seh(void)+0xff (FPO: [Non-Fpo]) (CONV: cdecl) [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl # 264]
02 c 0029fd9c 778037f5 7ffdf000 75826e8f 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
03 40 0029fddc 778037c8 01241652 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
04 18 0029fdf4 00000000 01241652 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
results to corroborate the edit that the Signature varies per session
cdb -c "$$>a< gcsex.txt" runasm.exe | grep -i Sign
Frameid = 0 FrameType = 1 Frame Signature = 1e7e
Frameid = 0 FrameType = 82 Frame Signature = 1e7e
Frameid = 0 FrameType = 81 Frame Signature = 1e7e
Frameid = 0 FrameType = 81 Frame Signature = 1e7e
Frameid = 0 FrameType = 81 Frame Signature = 1e7e
Frameid = 0 FrameType = 81 Frame Signature = 1e7e
cdb -c "$$>a< gcsex.txt" runasm.exe | grep -i Sign
Frameid = 0 FrameType = 1 Frame Signature = b649
Frameid = 0 FrameType = 82 Frame Signature = b649
Frameid = 0 FrameType = 81 Frame Signature = b649
Frameid = 0 FrameType = 81 Frame Signature = b649
Frameid = 0 FrameType = 81 Frame Signature = b649
Frameid = 0 FrameType = 81 Frame Signature = b649
cdb -c "$$>a< gcsex.txt" runasm.exe | grep -i Sign
Frameid = 0 FrameType = 1 Frame Signature = decf
Frameid = 0 FrameType = 82 Frame Signature = decf
Frameid = 0 FrameType = 81 Frame Signature = decf
Frameid = 0 FrameType = 81 Frame Signature = decf
Frameid = 0 FrameType = 81 Frame Signature = decf
Frameid = 0 FrameType = 81 Frame Signature = decf
cat gcsex.txt
.load gcsex
bp runasm!main "!gcsex;q"
g

This is using some wrappers, but the intention should be clear.
Using IDebugSymbols4::GetNameByInlineContext if the InlineFrameContext indicates that it is an inline frame, and IDebugSymbols4::GetSymbolNameByOffset otherwise.
C# example below:
ulong displacement;
var builder = new StringBuilder(256);
var isInlineFrame = frame.InlineFrameContext.FrameType.HasFlag(StackFrameType.Inline);
if (isInlineFrame)
{
_symbols5.GetNameByInlineContext(frame.InstructionOffset, frame.InlineFrameContext.ContextValue, ref builder, out displacement);
}
else {
_symbols5.GetSymbolNameByOffset(frame.InstructionOffset, ref builder, out displacement);
}
var name = builder.ToString();

Related

Does WinDBG support multi-line commands?

For the sake of readability, it would be nice to be able to have multi-line "script" (whatever the syntax of WinDBG is called) code.
For example, I want to search through a chunk of memory looking for things that look somewhat like a wxArrayString...
r $t1 = 0xe53801c
r $t2 = 0x2e800
.for (r $t3 = 0; #$t3 < 0x400; r $t3 = #$t3 + 1) {
.if (##c++(
((int)(((MyModule!wxArrayString*)(#$t1+#$t2+(#$t3*8)))->m_compareFunction) >= 0x10000) &&
((int)(((MyModule!wxArrayString*)(#$t1+#$t2+(#$t3*8)))->m_pItems) >= 0x10000) &&
(((MyModule!wxArrayString*)(#$t1+#$t2+(#$t3*8)))->m_nCount <= 5000) &&
(((MyModule!wxArrayString*)(#$t1+#$t2+(#$t3*8)))->m_nCount > 0) &&
(((MyModule!wxArrayString*)(#$t1+#$t2+(#$t3*8)))->m_nSize <= 10000) &&
(((MyModule!wxArrayString*)(#$t1+#$t2+(#$t3*8)))->m_nSize > 0)
)) {
dx ((MyModule!wxArrayString*)(#$t1+#$t2+(#$t3*8)))
}
}
Smushing that into a single line isn't too hard, but it would be nice if I didn't have to do it every time I wanted to test a change in the code.
Is there a way to do this (with line continuations or some other magic)?
If you have an editor that allows and supports files with different line endings (in one file!), it's feasible:
Insert Macintosh linebreaks (CR; 0x0D) where you want to see line breaks and Unix line breaks (LF; 0x0A) where you want WinDbg to end a statement.
$<, $><, $$<, $$><, $$ >a< (MSDN; not some injection) has some details:
The $$>< and $>< tokens execute the commands that are found in the script file literally, which means they open the script file, replace all carriage returns with semicolons, and execute the resulting text as a single command block.
Actually that statement is wrong. It replaces 0x0A by semicolons and that is LF (line feed) and not carriage return (CR).
Example:
Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000 2E 65 63 68 6F 20 22 57 6F 77 0D 2E 65 63 68 6F .echo "Wow..echo
00000010 20 74 68 65 72 65 20 69 73 20 61 20 6C 69 6E 65 there is a line
00000020 62 72 65 61 6B 22 0A 72 20 24 74 30 0A 6B 20 4C break".r $t0.k L
00000030 31
which looks like 4 lines but will be executed as 1 line:
2:004> $$>a< b:\newline.windbg
Wow .echo there is a linebreak
$t0=00000000
# ChildEBP RetAddr
00 0116f508 7726c088 ntdll!LdrpDoDebuggerBreak+0x2b
The WinDbg command parser, scripting parser and whatever parsers are implemented ... erm ... in a quite simplistic way, I assume. They didn't think much about escape characters etc. Note that they do not care about the " of .echo and would even replace even replace LF in the middle of a string. There are plenty of examples which are like that.
Maybe you want to look into Python scripting with pykd, implement a WinDbg extension or similar.
are those numbers/address 10000 for m_compareFunction , m_pItems an example and not actual value ??
m_compareFunction is a Function Pointer
0:000> ?? labels[0].m_compareFunction
<function> * 0x00000000`00000000
m_pItems is a wxString *
0:000> ?? labels[0].m_pItems
class wxString * 0x000001c8`2553f598
=00007ff7`cc3b8a60 npos : 0xffffffff`ffffffff
+0x000 m_impl : std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t> >
+0x028 m_convertedToChar : wxString::ConvertedBuffer<char>
also adding $t1+$t2 could be eliminated
anyway put your script in a txtfile like myscript.wds and use
$$>a< myscript.wds
to execute it
you can write script as multiline
:\>cat d:\myscript.wds
r? $t0 = labels
?? #$t0
?? #$t0[0]
?? #$t0[1].m_pItems
?? #$t0[3].m_nCount
:\>
executed
0:000> $$>a< d:\myscript.wds
class wxArrayString [8] 0x00000035`350fe650
+0x000 m_compareFunction : (null)
+0x008 m_nSize : 0x20
+0x010 m_nCount : 0x13
+0x018 m_pItems : 0x000001c8`2553f598 wxString
+0x020 m_autoSort : 0
class wxArrayString
+0x000 m_compareFunction : (null)
+0x008 m_nSize : 0x20
+0x010 m_nCount : 0x13
+0x018 m_pItems : 0x000001c8`2553f598 wxString
+0x020 m_autoSort : 0
class wxString * 0x000001c8`26f40fa8
=00007ff7`cc3b8a60 npos : 0xffffffff`ffffffff
+0x000 m_impl : std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t> >
+0x028 m_convertedToChar : wxString::ConvertedBuffer<char>
unsigned int64 0
also if you use r? you can eliminate all the casting
0:000> r #$t0 = 0x00000035`350fe654
0:000> r? $t19 = (widgets!wxArrayString *)#$t0
0:000> ?? #$t19
class wxArrayString * 0x00000035`350fe654
+0x000 m_compareFunction : 0x00000020`00000000 int +2000000000
+0x008 m_nSize : 0x00000013`00000000
+0x010 m_nCount : 0x2553f598`00000000
+0x018 m_pItems : 0x350ff800`000001c8 wxString
+0x020 m_autoSort : 35

How do loop over the search results for a byte string and offset the resultant pointer (in WinDbg)?

I'm attempting to search for an arbitrarily long byte string in WinDbg and print out the address if an integer in the vicinity meets some criteria.
Pseudo-register $t0 contains the starting address I want to search.
Here's something that, based on the Windows docs, maybe could work (though it clearly doesn't).
.foreach (place { s -[1] #$t0 L?30000 00 00 00 00 00 20 00 00 }) { .if ( (place +0x8) <= 0x1388) { .printf "0x%x\n", place } }
Search
First, the search command doesn't quite work correctly. I only want the address of the match (not the data).
s -[1] #$t0 L?30000 00 00 00 00 00 20 00 00
The docs say that the 1 flag will only return the address. When I issue that command, WinDbg replies
^ Syntax error in 's -1 #$t0 L?30000 00 00 00 00 00 20 00 00 '
If I leave out the -1, it finds two matches.
What am I doing wrong here?
Condition
I don't think the condition is behaving the way I want. I want to look at the third dword starting at place, i.e. place+8, and verify that it's smaller than 5000 (decimal). The .if inside the .foreach isn't printing a meaningful value for place (i.e. the address returned from the search). I think it's dereferencing place first and comparing the value of that integer to 5000. How do I look at the value of, say, *(int*)(place+8)?
Documentation?
The docs are not helping me very much. They only have sparse examples, none of which correspond to what I need.
Is there better documentation somewhere besides MS's Hardware Dev Center?
you can start writing JavaScript for a more legible way of scripting
old way
0:000> s -b vect l?0x1000 4d
00007ff7`8aaa0000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
00007ff7`8aaa00d4 4d 90 80 d2 df f9 82 d3-4d 90 80 d2 52 69 63 68 M.......M...Rich
00007ff7`8aaa00dc 4d 90 80 d2 52 69 63 68-4c 90 80 d2 00 00 00 00 M...RichL.......
0:000> s -[1]b vect l?0x1000 4d
0x00007ff7`8aaa0000
0x00007ff7`8aaa00d4
0x00007ff7`8aaa00dc
using javascript
function search(addr,len)
{
var index = []
var mem = host.memory.readMemoryValues(addr,len)
for(var i = 0; i < len; i++)
{
if(mem[i] == 0x4d)
{
index.push(addr+i)
}
}
return index
}
executed will return address like which you can manipulate further
0:000> dx -r1 #$scriptContents.search(0x00007ff78aaa0000,1000)
#$scriptContents.search(0x00007ff78aaa0000,1000) : 140701160046592,140701160046804,140701160046812
length : 0x3
[0x0] : 0x7ff78aaa0000
[0x1] : 0x7ff78aaa00d4
[0x2] : 0x7ff78aaa00dc
improving the script a little to find something based on first result
we will try to find the index of Rich string that follows the character 'M'
modified script
function search(addr,len)
{
var index = []
var Rich = []
var result = []
var mem = host.memory.readMemoryValues(addr,len)
for(var i = 0; i < len; i++)
{
if(mem[i] == 0x4d)
{
index.push(addr+i)
var temp = host.memory.readMemoryValues(addr+i+4,1,4)
host.diagnostics.debugLog(temp +"\t")
if(temp == 0x68636952)
{
Rich.push(addr+i)
}
}
}
result.push(index)
result.push(Rich)
return result
}
result only the third occurance of char "M" is followed by Rich string
0:000> dx -r2 #$scriptContents.search(0x00007ff78aaa0000,1000)
3 3548576223 1751345490 #$scriptContents.search(0x00007ff78aaa0000,1000) : 140701160046592,140701160046804,140701160046812,140701160046812
length : 0x2
[0x0] : 140701160046592,140701160046804,140701160046812
length : 0x3
[0x0] : 0x7ff78aaa0000
[0x1] : 0x7ff78aaa00d4
[0x2] : 0x7ff78aaa00dc
[0x1] : 140701160046812
length : 0x1
[0x0] : 0x7ff78aaa00dc
0:000> s -b vect l?0x1000 4d
00007ff7`8aaa0000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
00007ff7`8aaa00d4 4d 90 80 d2 df f9 82 d3-4d 90 80 d2 52 69 63 68 M.......M...Rich
00007ff7`8aaa00dc 4d 90 80 d2 52 69 63 68-4c 90 80 d2 00 00 00 00 M...RichL.......
load the extensension jsprovider.dll .load jsprovider
write a script say foo.js
load the script .scriptload ...\path\foo.js
execute any functions inside the js you wrote with dx #$scriptContents.myfunc(myargs)
see below using cdb just for ease of copy paste windbg works just as is
F:\>type mojo.js
function hola_mojo ()
{
host.diagnostics.debugLog("hola mojo this is javascript \n")
}
F:\>cdb -c ".load jsprovider;.scriptload .\mojo.js;dx #$scriptContents.hola_mojo();q" cdb | f:\usr\bin\grep.exe -A 6 -i reading
0:000> cdb: Reading initial command '.load jsprovider;.scriptload .\mojo.js;dx #$scriptContents.hola_mojo();q'
JavaScript script successfully loaded from 'F:\mojo.js'
hola mojo this is javascript
#$scriptContents.hola_mojo()
quit:
If I read this part of the documentation
s [-[[Flags]Type]] Range Pattern
correctly, you cannot leave out Type when specifying flags. That's because the flags are inside two square brackets. Otherwise it would have been noted as s [-[Flags][Type]] Range Pattern.
Considering this, the example works:
0:000> .dvalloc 2000
Allocated 2000 bytes starting at 00ba0000
0:000> eb 00ba0000 01 02 03 04 05 06 07 08 09
0:000> eb 00ba1000 01 02 03 04 05 06 07 08 09
0:000> s -[1]b 00ba0000 L?2000 01 02 03 04 05 06 07 08
0x00ba0000
0x00ba1000
Also note that you'll have a hidden bug for the use of place: it should be ${place}. By default, that will work with the address (line break for readability on SO):
0:000> .foreach (place {s -[1]b 00ba0000 L?2000 01 02 03 04 05 06 07 08 })
{ .if ( (${place} +0x8) < 0xba1000) { .printf "0x%x\n", ${place} } }
0xba0000
In order to read a DWord from that address, use the dwo() MASM oerator (line break for readability on SO):
0:000> .foreach (place {s -[1]b 00ba0000 L?2000 01 02 03 04 05 06 07 08 })
{ .if ( (dwo(${place} +0x8)) < 0xba1000)
{ .printf "0x%x = 0x%x\n", ${place}, dwo(${place}+8) } }
0xba0000 = 0x9
0xba1000 = 0x9

How to decode hex into separate bits in Perl?

A byte stored as two hex numbers contains a set of flags. I need to extract these flags, as 0's and 1's. Iterating over entries in a file with:
foreach(<>)
{
#line = split(/ /,$_);
$torR = !!((hex $line[4]) & 0x3); # bit 0 or 1
$torY = !!((hex $line[4]) & 0x4); # bit 2
$torG = !!((hex $line[4]) & 0x8); # bit 3
print "$torR,$torY,$torG\n";
}
run on data file:
796.129 [1f/01] len:7< 02 01 D5 01 8B 0A 8E
796.224 [1f/01] len:7< 02 01 D4 03 09 A9 B8
796.320 [1f/01] len:7< 00 01 D4 03 07 49 5A
796.415 [1f/01] len:7< 00 01 D4 00 11 A0 EE
796.515 [1f/01] len:7< 00 01 D4 00 00 31 4C
796.627 [1f/01] len:7< 02 01 D4 01 89 C1 FD
796.724 [1f/01] len:7< 02 01 D3 03 06 39 FD
796.820 [1f/01] len:7< 08 01 D4 03 08 40 6F
796.915 [1f/01] len:7< 08 01 D5 00 13 3D A4
797.015 [1f/01] len:7< 08 01 D4 00 00 34 04
Actual Result -
1,,
1,,
,,
,,
,,
1,,
1,,
,,1
,,1
,,1
Desired result:
1,0,0
1,0,0
0,0,0
0,0,0
0,0,0
1,0,0
1,0,0
0,0,1
0,0,1
0,0,1
Seems like 'false' gets stored as empty string instead of '0'.
Is there a neat trick to get this right at once, or do I need to convert empty strings to zeros "manually"?
If you want the true/false values to be numeric, you need to coerce them to be numeric:
$torR = 0 + !!((hex $line[4]) & 0x3); # bit 0 or 1
$torY = 0 + !!((hex $line[4]) & 0x4); # bit 2
$torG = 0 + !!((hex $line[4]) & 0x8); # bit 3
Keep in mind that the empty string '' is also a false value.
On the other hand, I might be inclined to write that as:
my (#ryg) = map 0 + !!((hex $line[4]) & $_), 0x3, 0x4, 0x5;
print join(', ', #ryg), "\n";
In addition, you would probably benefit from not using plain numbers in your program. Consider, for example, having a %FLAG structure that gives names to these constants, and a %COL structure that gives names to the columns you are interested in. Using the data you posted:
use Const::Fast;
const my %FLAG => (
torR => 0x3,
torY => 0x4,
torG => 0x5,
);
const my %COL => (
# ...
tor => 4,
);
while (my $line = <DATA>) {
my #line = split ' ', $line;
my %set_flags = map +($_ => 0 + !!((hex $line[$COL{tor}]) & $FLAG{$_})), qw(torR torY torG);
print join(', ', #set_flags{qw(torR torY torG)}), "\n";
}
__DATA__
796.129 [1f/01] len:7< 02 01 D5 01 8B 0A 8E
796.224 [1f/01] len:7< 02 01 D4 03 09 A9 B8
796.320 [1f/01] len:7< 00 01 D4 03 07 49 5A
796.415 [1f/01] len:7< 00 01 D4 00 11 A0 EE
796.515 [1f/01] len:7< 00 01 D4 00 00 31 4C
796.627 [1f/01] len:7< 02 01 D4 01 89 C1 FD
796.724 [1f/01] len:7< 02 01 D3 03 06 39 FD
796.820 [1f/01] len:7< 08 01 D4 03 08 40 6F
796.915 [1f/01] len:7< 08 01 D5 00 13 3D A4
797.015 [1f/01] len:7< 08 01 D4 00 00 34 04
I think I would use split and unpack to turn each value into an array of zeroes and ones, and then examine them individually
use strict;
use warnings 'all';
for my $val ( qw/ 02 02 00 00 00 01 01 08 08 08 / ) {
my #bits = split //, unpack 'b8', chr hex $val;
my $torR = $bits[0] || $bits[1] ? 1 : 0;
my $torY = $bits[2] ? 1 : 0;
my $torG = $bits[3] ? 1 : 0;
print "$torR,$torY,$torG\n";
}
output
1,0,0
1,0,0
0,0,0
0,0,0
0,0,0
1,0,0
1,0,0
0,0,1
0,0,1
0,0,1
Or here's a way using the Bit::Vector which produces the same result
use strict;
use warnings 'all';
use Bit::Vector;
for my $val ( qw/ 02 02 00 00 00 01 01 08 08 08 / ) {
my $vec = Bit::Vector->new_Hex(8, $val);
my $torR = $vec->Chunk_Read(2, 0) ? 1 : 0;
my $torY = $vec->Chunk_Read(1, 2) ? 1 : 0;
my $torG = $vec->Chunk_Read(1, 3) ? 1 : 0;
print "$torR,$torY,$torG\n";
}

Functions to deal with Unicode string manipulation with PowerBuilder 12.1

I am currently working on converting our PowerBuilder 12.1 application, which does not currently support Unicode, into a Unicode supporting application.
I have made some modifications to save Unicode data to our database, as well to files, but I have hit a slight snag in processing strings.
For example, the character 𠆾 is a Surrogate Pair and PowerBuilder interprets this as 2 characters (similar to how .NET operates). Thus:
LEN("𠆾") = 2
To me, this part makes sense, as it is count each code unit as a character.
Currently we have come up with two solutions to handle doing string functions with Unicode characters:
Callable OLEObjects written in C# .NET
using the PBNI interface to call C# .NET (want to stay away from this solution if possible)
An example of the .NET code we are thinking of using for determining the string length is:
StringInfo.ParseCombiningCharacters("𠆾").Length = 1
We are just worried about the impact on performance with constantly calling the OLEObjects/PBNI to do all of our string processing. Have any of the other PowerBuilder developers here done Unicode string manipulation (LEN, MID, POS, etc), and how did you do it?
Thank you.
This is in response to Seki's hex conversion function. I'm posting it as an answer so I can include source code. I use the Microsoft cryptographic functions to display blobs in my debugging tools. Here's a simplified version of my blob window. The one I use is PFC-based and uses an object that wraps the MS Crypto library. It's from PB 12.5 but should import into any Unicode version of PB.
HA$PBExportHeader$w_show_blob.srw
forward
global type w_show_blob from window
end type
type sle_1 from singlelineedit within w_show_blob
end type
type mle_1 from multilineedit within w_show_blob
end type
end forward
global type w_show_blob from window
integer width = 3081
integer height = 1988
boolean titlebar = true
boolean controlmenu = true
boolean minbox = true
boolean maxbox = true
boolean resizable = true
boolean center = true
sle_1 sle_1
mle_1 mle_1
end type
global w_show_blob w_show_blob
type prototypes
FUNCTION boolean CryptBinaryToString ( &
Blob pbBinary, &
ulong cbBinary, &
ulong dwFlags, &
Ref string pszString, &
Ref ulong pcchString ) &
LIBRARY "crypt32.dll" ALIAS FOR "CryptBinaryToStringW"
end prototypes
type variables
CONSTANT Ulong CRYPT_STRING_HEXASCIIADDR = 11
end variables
forward prototypes
public subroutine of_showblob (ref blob abl_data)
end prototypes
public subroutine of_showblob (ref blob abl_data);unsignedlong lul_size, lul_bufsize
string ls_hex
try
lul_size = len(abl_data)
sle_1.text = string(lul_size)
setnull(ls_hex)
cryptbinarytostring( abl_data, lul_size, CRYPT_STRING_HEXASCIIADDR, ls_hex, lul_bufsize)
ls_hex = space(lul_bufsize)
if not cryptbinarytostring( abl_data, lul_size, CRYPT_STRING_HEXASCIIADDR , ls_hex, lul_bufsize) then
mle_1.text = "error converting blob data"
else
mle_1.text = ls_hex
end if
catch(runtimeerror re)
messagebox("oops", re.text)
end try
end subroutine
on w_show_blob.create
this.sle_1=create sle_1
this.mle_1=create mle_1
this.Control[]={this.sle_1,&
this.mle_1}
end on
on w_show_blob.destroy
destroy(this.sle_1)
destroy(this.mle_1)
end on
type sle_1 from singlelineedit within w_show_blob
integer x = 73
integer width = 517
integer height = 88
integer taborder = 10
integer textsize = -10
integer weight = 400
fontcharset fontcharset = ansi!
fontpitch fontpitch = variable!
fontfamily fontfamily = swiss!
string facename = "Arial"
long textcolor = 33554432
long backcolor = 553648127
string text = "none"
boolean displayonly = true
borderstyle borderstyle = stylelowered!
end type
type mle_1 from multilineedit within w_show_blob
integer x = 64
integer y = 96
integer width = 2898
integer height = 1716
integer taborder = 10
integer textsize = -10
integer weight = 400
fontcharset fontcharset = ansi!
fontpitch fontpitch = fixed!
fontfamily fontfamily = modern!
string facename = "Courier New"
long textcolor = 33554432
string text = "none"
boolean hscrollbar = true
boolean vscrollbar = true
boolean displayonly = true
borderstyle borderstyle = stylelowered!
end type
To use it, assuming your blob is lbl_myBlob:
open(w_show_blob)
w_show_blob.of_showblob(lbl_myBlob)
The output in the MLE looks like this:
0000 42 4d ee 00 00 00 00 00 00 00 76 00 00 00 28 00 BM........v...(.
0010 00 00 10 00 00 00 0f 00 00 00 01 00 04 00 00 00 ................
0020 00 00 78 00 00 00 00 00 00 00 00 00 00 00 00 00 ..x.............
0030 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 80 ................
0040 00 00 00 80 80 00 80 00 00 00 80 00 80 00 80 80 ................
0050 00 00 80 80 80 00 c0 c0 c0 00 00 00 ff 00 00 ff ................
0060 00 00 00 ff ff 00 ff 00 00 00 ff 00 ff 00 ff ff ................
0070 00 00 ff ff ff 00 88 88 88 88 88 88 88 88 88 88 ................
0080 80 88 88 88 88 88 88 88 80 08 88 88 88 88 88 88 ................
0090 80 00 88 88 88 88 88 88 80 00 08 88 88 88 88 88 ................
00a0 80 00 00 88 88 88 88 88 80 00 00 08 88 88 88 88 ................
00b0 80 00 00 00 88 88 88 88 80 00 00 08 88 88 88 88 ................
00c0 80 00 00 88 88 88 88 88 80 00 08 88 88 88 88 88 ................
00d0 80 00 88 88 88 88 88 88 80 08 88 88 88 88 88 88 ................
00e0 80 88 88 88 88 88 88 88 88 88 88 88 88 88 ..............
Since the release 10, PB is unicode (utf-16le)-aware. So the legacy Len() is implicit LenW() (as other string functions, and dealing with legacy data could imply to use explicit LenA()).
Are you sure that you are getting some utf-16le encoding ? Given the following function, what does it return on a string containing your data, if you call it with hexdump_blob(blob(your_string))?
Paste this code into the source code of a new global function named hexdump_blob to have an hexadecimal display (hex editor like) for blob contents.
global type hexdump_blob from function_object
end type
forward prototypes
global function string hexdump_blob (blob abl_data, boolean ab_fill_lastline)
end prototypes
global function string hexdump_blob (blob abl_data, boolean ab_fill_lastline);//hexify a blob content
string ls_tohex = "0123456789ABCDEF"
string ls_msg = "", ls_line, ls_binary
long i, j, length
byte b
string ls_fill
if isnull( abl_data ) then return ""
if ab_fill_lastline then
ls_fill = " __"
else
ls_fill = " "
end if
length = len( abl_data )
for i = 1 to length
GetByte( abl_data, i, b )
ls_line += mid( ls_tohex, 1+ mod(int(b/16),16), 1)
ls_line += mid( ls_tohex, 1+ mod(b,16), 1)
ls_line += " "
ls_binary += string( iif(b>31 and b<128,char(b)," "))
if mod(i,16) = 0 and i > 0 then
ls_binary = replaceall( ls_binary, "~r", "·") //no cr/lf
ls_binary = replaceall( ls_binary, "~n", "·")
ls_binary = replaceall( ls_binary, "~t", "·")
ls_msg += "[" + string( i - 16, "0000") + "] " + ls_line + "~t" + ls_binary + "~r~n"
ls_line = ""
ls_binary = ""
end if
next
i -- // i - 1 due to the last loop in for
ls_line += fill(ls_fill, 3 * ( 16 - mod(i, 16) ) )
ls_msg += "[" + string( i - mod(i,16), "0000") + "] " + ls_line + "~t" + ls_binary
return ls_msg
end function
Also, here is the replaceall() function that is used by hexdump_blob()
global type replaceall from function_object
end type
forward prototypes
global function string replaceall (string as_source, string as_pattern, string as_replace)
end prototypes
global function string replaceall (string as_source, string as_pattern, string as_replace);//remplace toute les occurences de as_pattern de as_source par as_replace
string ls_target
long i, j
ls_target=""
i = 1
j = 1
do
i = pos( as_source, as_pattern, j )
if i>0 then
ls_target += mid( as_source, j, i - j )
ls_target += as_replace
j = i + len( as_pattern )
else
ls_target += mid( as_source, j )
end if
loop while i>0
return ls_target
end function
and the iif() that simulates the C ternary operator, or the visual basic iif()
global type iif from function_object
end type
forward prototypes
global function any iif (boolean ab_cond, any aa_true, any aa_false)
end prototypes
global function any iif (boolean ab_cond, any aa_true, any aa_false);
// simulates the VB iif or C ternary operator
if ab_cond then
return aa_true
else
return aa_false
end if
end function
Wouldn't you want to use the LenA() method?
http://www.techno-kitten.com/Changes_to_PowerBuilder/New_in_PowerBuilder_10/PB10New_-_Unicode_Support/PB10New_-_Unicode_Related_Chan/PB10New_-_String-Related_Funct/pb10new_-_modified_processing_.html

How do I find what is the value of offset of this byte?

So somehow from the following hex data (03 00 21 04 80 04) the values below were obtained.
Can anybody can tell how how can I do this and how it was achieved?
Band = 3 (40,6)
Duplex_Mode = 0 (46,1)
Result = 0 (47,1)
Reserved_1 = 0 (48,8)
Min_Search_Half_Frames = 1 (56,5)
Min_Search_Half_Frames_Early_Abort = 1 (61,5)
Max_Search_Half_Frames = 1 (66,5)
Max_PBCH_Frames = 0 (71,5)
Number_of_Blocked_Cells = 0 (76,3)
Number_PBCH_Decode_Attemp_Cells = 1 (79,3)
Number_of_Search_Results = 1 (82,4)
Reserved_2 = 0 (86,2)
The parameters in paranthesis is the Offset/Length I am told. I don't understand how based on that information should I be able to unpack this payload.
So I have written
my $data = pack ('C*', map hex, split /\s+/, "03 00 21 04 80 04");
($tmp1, $Reserved_1, $tmp2) = unpack("C C V", $data);
And now help. How do I unpack the table values above from $tmp1 and $tmp2 ?
EDIT: Hex Data = "00 00 00 7F 08 03 00 21 04 80 04 FF D7 FB 0C EC 01 44 00 61 1D 00 00 10 3B 00 00 FF D7 FB 0C 00 00 8C 64 00 00 EC 45"
Thanks!
You might want to define a set of bitmasks, and use bitwise AND operations to unpack your data.