Skip to content

Commit 0d0218b

Browse files
committed
add example render layer to inject newlines
1 parent 876ac63 commit 0d0218b

File tree

1 file changed

+215
-0
lines changed

1 file changed

+215
-0
lines changed
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
from typing import List
2+
3+
import binaryninja
4+
from binaryninja import RenderLayer, InstructionTextToken, \
5+
InstructionTextTokenType, DisassemblyTextLine, log_info
6+
7+
8+
"""
9+
Render Layer that splits string literals containing newline characters into multiple lines.
10+
11+
Before:
12+
char* s = "Hi, guys!\nWe all love Binja!";
13+
14+
After:
15+
char* s = "Hi, guys!\n"
16+
"We all love Binja!";
17+
"""
18+
19+
20+
class NewlineSplitRenderLayer(RenderLayer):
21+
name = "Split Strings at Newlines"
22+
23+
def split_string_token(self, token: InstructionTextToken) -> List[InstructionTextToken]:
24+
"""
25+
Split a string token containing \n into multiple tokens.
26+
Returns a list of tokens representing the split string.
27+
"""
28+
text = token.text
29+
log_info(f"[NewlineSplitRenderLayer] Examining token: type={token.type}, text={repr(text)}")
30+
31+
# Check if this token contains \n (the token text doesn't include quotes)
32+
if '\\n' not in text:
33+
return [token]
34+
35+
log_info(f"[NewlineSplitRenderLayer] Found string with \\n: {repr(text)}")
36+
37+
# The text is the string content (no quotes in token.text)
38+
content = text
39+
40+
# Split by \n but keep the \n with the preceding part
41+
parts = []
42+
current_part = ""
43+
i = 0
44+
while i < len(content):
45+
if i < len(content) - 1 and content[i] == '\\' and content[i+1] == 'n':
46+
current_part += '\\n'
47+
parts.append(current_part)
48+
current_part = ""
49+
i += 2
50+
else:
51+
current_part += content[i]
52+
i += 1
53+
54+
# Add any remaining content
55+
if current_part:
56+
parts.append(current_part)
57+
58+
# If we only have one part, return the original token
59+
if len(parts) <= 1:
60+
return [token]
61+
62+
log_info(f"[NewlineSplitRenderLayer] Splitting into {len(parts)} parts: {parts}")
63+
64+
# Create tokens for each part (token.text doesn't include quotes, so don't add them)
65+
result = []
66+
for i, part in enumerate(parts):
67+
result.append(InstructionTextToken(token.type, part, token.value, token.size, token.operand, token.context, token.address, token.confidence))
68+
69+
return result
70+
71+
def apply_to_block(
72+
self,
73+
block: 'binaryninja.BasicBlock',
74+
lines: List['binaryninja.DisassemblyTextLine']
75+
):
76+
log_info(f"[NewlineSplitRenderLayer] apply_to_block called with {len(lines)} lines")
77+
new_lines = []
78+
79+
for line in lines:
80+
# Look for string tokens
81+
has_split = False
82+
split_info = None
83+
84+
for i, token in enumerate(line.tokens):
85+
if token.type == InstructionTextTokenType.StringToken:
86+
log_info(f"[NewlineSplitRenderLayer] Found StringToken at index {i}")
87+
split_tokens = self.split_string_token(token)
88+
if len(split_tokens) > 1:
89+
has_split = True
90+
split_info = (i, split_tokens)
91+
log_info(f"[NewlineSplitRenderLayer] Will split this line into {len(split_tokens)} parts")
92+
break
93+
94+
if not has_split:
95+
new_lines.append(line)
96+
continue
97+
98+
# Create multiple lines for the split string
99+
token_idx, split_tokens = split_info
100+
101+
# Find the indentation level by looking at the position of the string token
102+
indent_count = 0
103+
for i in range(token_idx):
104+
if line.tokens[i].type == InstructionTextTokenType.TextToken:
105+
indent_count += len(line.tokens[i].text)
106+
else:
107+
indent_count += len(line.tokens[i].text)
108+
109+
# Create first line with original tokens up to and including first split token
110+
first_line = DisassemblyTextLine()
111+
first_line.address = line.address
112+
first_line.il_instruction = line.il_instruction
113+
first_line.highlight = line.highlight
114+
115+
first_line.tokens = line.tokens[:token_idx] + [split_tokens[0]]
116+
new_lines.append(first_line)
117+
118+
# Create continuation lines for remaining split tokens
119+
for j in range(1, len(split_tokens)):
120+
cont_line = DisassemblyTextLine()
121+
cont_line.address = line.address
122+
cont_line.il_instruction = line.il_instruction
123+
cont_line.highlight = line.highlight
124+
125+
# Add indentation to align with the start of the string
126+
indent_token = InstructionTextToken(InstructionTextTokenType.TextToken, ' ' * indent_count)
127+
cont_line.tokens = [indent_token, split_tokens[j]]
128+
129+
# If this is the last split token, add the rest of the original tokens
130+
if j == len(split_tokens) - 1:
131+
cont_line.tokens.extend(line.tokens[token_idx + 1:])
132+
133+
new_lines.append(cont_line)
134+
135+
return new_lines
136+
137+
def apply_to_high_level_il_body(
138+
self,
139+
function: 'binaryninja.Function',
140+
lines: List['binaryninja.LinearDisassemblyLine']
141+
):
142+
log_info(f"[NewlineSplitRenderLayer] apply_to_high_level_il_body called with {len(lines)} lines")
143+
# Similar logic for HLIL linear view
144+
new_lines = []
145+
146+
for line in lines:
147+
# Look for string tokens
148+
has_split = False
149+
split_info = None
150+
151+
for i, token in enumerate(line.contents.tokens):
152+
if token.type == InstructionTextTokenType.StringToken:
153+
split_tokens = self.split_string_token(token)
154+
if len(split_tokens) > 1:
155+
has_split = True
156+
split_info = (i, split_tokens)
157+
break
158+
159+
if not has_split:
160+
new_lines.append(line)
161+
continue
162+
163+
# Create multiple lines for the split string
164+
token_idx, split_tokens = split_info
165+
166+
# Find the indentation level
167+
indent_count = 0
168+
for i in range(token_idx):
169+
indent_count += len(line.contents.tokens[i].text)
170+
171+
# Create first line - we need to create a new DisassemblyTextLine with modified tokens
172+
first_tokens = line.contents.tokens[:token_idx] + [split_tokens[0]]
173+
first_contents = DisassemblyTextLine(first_tokens, line.contents.address)
174+
first_contents.highlight = line.contents.highlight
175+
first_contents.il_instruction = line.contents.il_instruction
176+
177+
first_line = binaryninja.LinearDisassemblyLine(
178+
line.type,
179+
line.function,
180+
line.block,
181+
first_contents
182+
)
183+
new_lines.append(first_line)
184+
185+
# Create continuation lines
186+
for j in range(1, len(split_tokens)):
187+
# Add indentation
188+
indent_token = InstructionTextToken(InstructionTextTokenType.TextToken, ' ' * indent_count)
189+
cont_tokens = [indent_token, split_tokens[j]]
190+
191+
# If this is the last split token, add the rest of the original tokens
192+
if j == len(split_tokens) - 1:
193+
cont_tokens.extend(line.contents.tokens[token_idx + 1:])
194+
195+
cont_contents = DisassemblyTextLine(cont_tokens, line.contents.address)
196+
cont_contents.highlight = line.contents.highlight
197+
cont_contents.il_instruction = line.contents.il_instruction
198+
199+
cont_line = binaryninja.LinearDisassemblyLine(
200+
line.type,
201+
line.function,
202+
line.block,
203+
cont_contents
204+
)
205+
206+
new_lines.append(cont_line)
207+
208+
return new_lines
209+
210+
def apply_to_flow_graph(self, graph: 'binaryninja.FlowGraph'):
211+
# Don't modify flow graphs
212+
pass
213+
214+
215+
NewlineSplitRenderLayer.register()

0 commit comments

Comments
 (0)