smda 4.2.2__tar.gz → 4.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. {smda-4.2.2/src/smda.egg-info → smda-4.2.3}/PKG-INFO +2 -1
  2. {smda-4.2.2 → smda-4.2.3}/README.md +1 -0
  3. {smda-4.2.2 → smda-4.2.3}/src/smda/SmdaConfig.py +1 -1
  4. smda-4.2.3/src/smda/__init__.py +1 -0
  5. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/PeSymbolProvider.py +13 -11
  6. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/RustSymbolProvider.py +12 -12
  7. {smda-4.2.2 → smda-4.2.3}/src/smda/intel/FunctionCandidateManager.py +18 -4
  8. {smda-4.2.2 → smda-4.2.3/src/smda.egg-info}/PKG-INFO +2 -1
  9. {smda-4.2.2 → smda-4.2.3}/tests/testPdataExtraction.py +28 -0
  10. {smda-4.2.2 → smda-4.2.3}/tests/testPeSymbolProvider.py +40 -6
  11. {smda-4.2.2 → smda-4.2.3}/tests/testRustSymbolProvider.py +34 -3
  12. smda-4.2.2/src/smda/__init__.py +0 -1
  13. {smda-4.2.2 → smda-4.2.3}/LICENSE +0 -0
  14. {smda-4.2.2 → smda-4.2.3}/pyproject.toml +0 -0
  15. {smda-4.2.2 → smda-4.2.3}/setup.cfg +0 -0
  16. {smda-4.2.2 → smda-4.2.3}/src/smda/Disassembler.py +0 -0
  17. {smda-4.2.2 → smda-4.2.3}/src/smda/DisassemblyResult.py +0 -0
  18. {smda-4.2.2 → smda-4.2.3}/src/smda/DisassemblyStatistics.py +0 -0
  19. {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/AArch64Backend.py +0 -0
  20. {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/AArch64CapstoneVerification.py +0 -0
  21. {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/AArch64Disassembler.py +0 -0
  22. {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/AArch64InstructionEscaper.py +0 -0
  23. {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/FunctionAnalysisState.py +0 -0
  24. {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/FunctionCandidateManager.py +0 -0
  25. {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/__init__.py +0 -0
  26. {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/analyzers.py +0 -0
  27. {smda-4.2.2 → smda-4.2.3}/src/smda/aarch64/definitions.py +0 -0
  28. {smda-4.2.2 → smda-4.2.3}/src/smda/cil/CilDisassembler.py +0 -0
  29. {smda-4.2.2 → smda-4.2.3}/src/smda/cil/CilInstructionEscaper.py +0 -0
  30. {smda-4.2.2 → smda-4.2.3}/src/smda/cil/FunctionAnalysisState.py +0 -0
  31. {smda-4.2.2 → smda-4.2.3}/src/smda/cil/__init__.py +0 -0
  32. {smda-4.2.2 → smda-4.2.3}/src/smda/common/BasicBlock.py +0 -0
  33. {smda-4.2.2 → smda-4.2.3}/src/smda/common/BinaryInfo.py +0 -0
  34. {smda-4.2.2 → smda-4.2.3}/src/smda/common/BlockLocator.py +0 -0
  35. {smda-4.2.2 → smda-4.2.3}/src/smda/common/CodeXref.py +0 -0
  36. {smda-4.2.2 → smda-4.2.3}/src/smda/common/DominatorTree.py +0 -0
  37. {smda-4.2.2 → smda-4.2.3}/src/smda/common/ExceptionHandling.py +0 -0
  38. {smda-4.2.2 → smda-4.2.3}/src/smda/common/RecursiveDisassembler.py +0 -0
  39. {smda-4.2.2 → smda-4.2.3}/src/smda/common/SmdaBasicBlock.py +0 -0
  40. {smda-4.2.2 → smda-4.2.3}/src/smda/common/SmdaFunction.py +0 -0
  41. {smda-4.2.2 → smda-4.2.3}/src/smda/common/SmdaInstruction.py +0 -0
  42. {smda-4.2.2 → smda-4.2.3}/src/smda/common/SmdaReport.py +0 -0
  43. {smda-4.2.2 → smda-4.2.3}/src/smda/common/TailcallAnalyzer.py +0 -0
  44. {smda-4.2.2 → smda-4.2.3}/src/smda/common/Tarjan.py +0 -0
  45. {smda-4.2.2 → smda-4.2.3}/src/smda/common/__init__.py +0 -0
  46. {smda-4.2.2 → smda-4.2.3}/src/smda/common/arch/ArchBackend.py +0 -0
  47. {smda-4.2.2 → smda-4.2.3}/src/smda/common/arch/__init__.py +0 -0
  48. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/AbstractLabelProvider.py +0 -0
  49. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/CilSymbolProvider.py +0 -0
  50. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/DelphiKbSymbolProvider.py +0 -0
  51. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/DelphiPythiaProvider.py +0 -0
  52. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/DelphiReSymProvider.py +0 -0
  53. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/ElfApiResolver.py +0 -0
  54. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/ElfSymbolProvider.py +0 -0
  55. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/GoLabelProvider.py +0 -0
  56. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/MachoDemangler.py +0 -0
  57. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/MachoSymbolProvider.py +0 -0
  58. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/OrdinalHelper.py +0 -0
  59. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/PdbSymbolProvider.py +0 -0
  60. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/WinApiResolver.py +0 -0
  61. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/__init__.py +0 -0
  62. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/import_parsers.py +0 -0
  63. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/rust_demangler/__init__.py +0 -0
  64. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/rust_demangler/main.py +0 -0
  65. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/rust_demangler/rust.py +0 -0
  66. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/rust_demangler/rust_legacy.py +0 -0
  67. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/rust_demangler/rust_v0.py +0 -0
  68. {smda-4.2.2 → smda-4.2.3}/src/smda/common/labelprovider/rust_demangler/utils.py +0 -0
  69. {smda-4.2.2 → smda-4.2.3}/src/smda/dalvik/DalvikDisassembler.py +0 -0
  70. {smda-4.2.2 → smda-4.2.3}/src/smda/dalvik/DalvikFunctionAnalysisState.py +0 -0
  71. {smda-4.2.2 → smda-4.2.3}/src/smda/dalvik/DalvikOpcodeDecoder.py +0 -0
  72. {smda-4.2.2 → smda-4.2.3}/src/smda/dalvik/__init__.py +0 -0
  73. {smda-4.2.2 → smda-4.2.3}/src/smda/ida/BackendInterface.py +0 -0
  74. {smda-4.2.2 → smda-4.2.3}/src/smda/ida/IdaExporter.py +0 -0
  75. {smda-4.2.2 → smda-4.2.3}/src/smda/ida/IdaInterface.py +0 -0
  76. {smda-4.2.2 → smda-4.2.3}/src/smda/ida/__init__.py +0 -0
  77. {smda-4.2.2 → smda-4.2.3}/src/smda/intel/BitnessAnalyzer.py +0 -0
  78. {smda-4.2.2 → smda-4.2.3}/src/smda/intel/FunctionAnalysisState.py +0 -0
  79. {smda-4.2.2 → smda-4.2.3}/src/smda/intel/FunctionCandidate.py +0 -0
  80. {smda-4.2.2 → smda-4.2.3}/src/smda/intel/IndirectCallAnalyzer.py +0 -0
  81. {smda-4.2.2 → smda-4.2.3}/src/smda/intel/IntelDisassembler.py +0 -0
  82. {smda-4.2.2 → smda-4.2.3}/src/smda/intel/IntelInstructionEscaper.py +0 -0
  83. {smda-4.2.2 → smda-4.2.3}/src/smda/intel/JumpTableAnalyzer.py +0 -0
  84. {smda-4.2.2 → smda-4.2.3}/src/smda/intel/LanguageAnalyzer.py +0 -0
  85. {smda-4.2.2 → smda-4.2.3}/src/smda/intel/MnemonicTfIdf.py +0 -0
  86. {smda-4.2.2 → smda-4.2.3}/src/smda/intel/X86Backend.py +0 -0
  87. {smda-4.2.2 → smda-4.2.3}/src/smda/intel/__init__.py +0 -0
  88. {smda-4.2.2 → smda-4.2.3}/src/smda/intel/definitions.py +0 -0
  89. {smda-4.2.2 → smda-4.2.3}/src/smda/utility/BracketQueue.py +0 -0
  90. {smda-4.2.2 → smda-4.2.3}/src/smda/utility/DelphiKbFileLoader.py +0 -0
  91. {smda-4.2.2 → smda-4.2.3}/src/smda/utility/DexFileLoader.py +0 -0
  92. {smda-4.2.2 → smda-4.2.3}/src/smda/utility/ElfFileLoader.py +0 -0
  93. {smda-4.2.2 → smda-4.2.3}/src/smda/utility/FileLoader.py +0 -0
  94. {smda-4.2.2 → smda-4.2.3}/src/smda/utility/MachoBinary.py +0 -0
  95. {smda-4.2.2 → smda-4.2.3}/src/smda/utility/MachoFileLoader.py +0 -0
  96. {smda-4.2.2 → smda-4.2.3}/src/smda/utility/MemoryFileLoader.py +0 -0
  97. {smda-4.2.2 → smda-4.2.3}/src/smda/utility/PeFileLoader.py +0 -0
  98. {smda-4.2.2 → smda-4.2.3}/src/smda/utility/PriorityQueue.py +0 -0
  99. {smda-4.2.2 → smda-4.2.3}/src/smda/utility/StringExtractor.py +0 -0
  100. {smda-4.2.2 → smda-4.2.3}/src/smda/utility/__init__.py +0 -0
  101. {smda-4.2.2 → smda-4.2.3}/src/smda/utility/common.py +0 -0
  102. {smda-4.2.2 → smda-4.2.3}/src/smda.egg-info/SOURCES.txt +0 -0
  103. {smda-4.2.2 → smda-4.2.3}/src/smda.egg-info/dependency_links.txt +0 -0
  104. {smda-4.2.2 → smda-4.2.3}/src/smda.egg-info/requires.txt +0 -0
  105. {smda-4.2.2 → smda-4.2.3}/src/smda.egg-info/top_level.txt +0 -0
  106. {smda-4.2.2 → smda-4.2.3}/tests/testAArch64Disassembler.py +0 -0
  107. {smda-4.2.2 → smda-4.2.3}/tests/testAArch64EscaperCapstoneVerification.py +0 -0
  108. {smda-4.2.2 → smda-4.2.3}/tests/testAArch64EscaperCoverage.py +0 -0
  109. {smda-4.2.2 → smda-4.2.3}/tests/testAArch64EscaperImproved.py +0 -0
  110. {smda-4.2.2 → smda-4.2.3}/tests/testAArch64MachoCorpus.py +0 -0
  111. {smda-4.2.2 → smda-4.2.3}/tests/testBenchmarkEval.py +0 -0
  112. {smda-4.2.2 → smda-4.2.3}/tests/testBitnessAnalyzer.py +0 -0
  113. {smda-4.2.2 → smda-4.2.3}/tests/testBracketQueue.py +0 -0
  114. {smda-4.2.2 → smda-4.2.3}/tests/testCandidateSafeguards.py +0 -0
  115. {smda-4.2.2 → smda-4.2.3}/tests/testCommonModels.py +0 -0
  116. {smda-4.2.2 → smda-4.2.3}/tests/testDalvikDisassembler.py +0 -0
  117. {smda-4.2.2 → smda-4.2.3}/tests/testDefinitionsExpansion.py +0 -0
  118. {smda-4.2.2 → smda-4.2.3}/tests/testDelphiPythiaProvider.py +0 -0
  119. {smda-4.2.2 → smda-4.2.3}/tests/testElfSymbolProvider.py +0 -0
  120. {smda-4.2.2 → smda-4.2.3}/tests/testEscaper.py +0 -0
  121. {smda-4.2.2 → smda-4.2.3}/tests/testExceptionHandling.py +0 -0
  122. {smda-4.2.2 → smda-4.2.3}/tests/testFileFormatParsers.py +0 -0
  123. {smda-4.2.2 → smda-4.2.3}/tests/testIndirectCallAnalyzer.py +0 -0
  124. {smda-4.2.2 → smda-4.2.3}/tests/testIntegration.py +0 -0
  125. {smda-4.2.2 → smda-4.2.3}/tests/testIntelDisassembler.py +0 -0
  126. {smda-4.2.2 → smda-4.2.3}/tests/testLanguageAnalyzer.py +0 -0
  127. {smda-4.2.2 → smda-4.2.3}/tests/testLateCandidateProduction.py +0 -0
  128. {smda-4.2.2 → smda-4.2.3}/tests/testMachoSymbolProvider.py +0 -0
  129. {smda-4.2.2 → smda-4.2.3}/tests/testPeFileLoader.py +0 -0
  130. {smda-4.2.2 → smda-4.2.3}/tests/testSmdaInstructionDetail.py +0 -0
  131. {smda-4.2.2 → smda-4.2.3}/tests/testSmdaInstructionDetailAArch64.py +0 -0
  132. {smda-4.2.2 → smda-4.2.3}/tests/testSmdaReportBuffer.py +0 -0
  133. {smda-4.2.2 → smda-4.2.3}/tests/testTarjan.py +0 -0
  134. {smda-4.2.2 → smda-4.2.3}/tests/testXmetadataNormalization.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smda
3
- Version: 4.2.2
3
+ Version: 4.2.3
4
4
  Summary: A recursive disassmbler optimized for CFG recovery from memory dumps. Based on capstone.
5
5
  Author-email: Daniel Plohmann <daniel.plohmann@mailbox.org>
6
6
  License: BSD 2-Clause
@@ -146,6 +146,7 @@ make test
146
146
 
147
147
  ## Version History
148
148
 
149
+ * 2026-07-10: v4.2.3 - Fix: function promotion bug caused by missing symbol type evaluation (THX: @r0ny123).
149
150
  * 2026-07-09: v4.2.2 - Now also parsing delay import tables from Windows PEs.
150
151
  * 2026-07-09: v4.2.1 - Better detection of CFG instructions with prefixes, improved accuracy of gap search. (THX: @r0ny123) IDA ARM64 export.
151
152
  * 2026-06-24: v4.2.0 - Further improvements for inter-procedural operand escaping (closer to x86_x64 and traditional PIC hashing).
@@ -105,6 +105,7 @@ make test
105
105
 
106
106
  ## Version History
107
107
 
108
+ * 2026-07-10: v4.2.3 - Fix: function promotion bug caused by missing symbol type evaluation (THX: @r0ny123).
108
109
  * 2026-07-09: v4.2.2 - Now also parsing delay import tables from Windows PEs.
109
110
  * 2026-07-09: v4.2.1 - Better detection of CFG instructions with prefixes, improved accuracy of gap search. (THX: @r0ny123) IDA ARM64 export.
110
111
  * 2026-06-24: v4.2.0 - Further improvements for inter-procedural operand escaping (closer to x86_x64 and traditional PIC hashing).
@@ -4,7 +4,7 @@ import os
4
4
 
5
5
  class SmdaConfig:
6
6
  # keep this in sync with smda.__version__
7
- VERSION = "4.2.2"
7
+ VERSION = "4.2.3"
8
8
  ESCAPER_DOWNWARD_COMPATIBILITY = "1.13.16"
9
9
  CONFIG_FILE_PATH = str(os.path.abspath(__file__))
10
10
  PROJECT_ROOT = str(os.path.abspath(os.sep.join([CONFIG_FILE_PATH, "..", "..", ".."])))
@@ -0,0 +1 @@
1
+ __version__ = "4.2.3"
@@ -53,7 +53,14 @@ class PeSymbolProvider(AbstractLabelProvider):
53
53
  def parseExports(self, lief_binary, base_addr=None):
54
54
  active_base = self._resolve_base_addr(lief_binary, base_addr)
55
55
  function_symbols = {}
56
- for function in lief_binary.exported_functions:
56
+ export = lief_binary.get_export()
57
+ if export is None:
58
+ return function_symbols
59
+ for function in export.entries:
60
+ if function.is_extern or function.is_forwarded:
61
+ # forwarder/extern entries redirect to another module's export and have no
62
+ # local address (LIEF sets .address to 0 for them) - not a local function.
63
+ continue
57
64
  function_name = ""
58
65
  with contextlib.suppress(UnicodeDecodeError, AttributeError):
59
66
  # here may occur a LIEF exception that we want to skip ->
@@ -65,25 +72,20 @@ class PeSymbolProvider(AbstractLabelProvider):
65
72
 
66
73
  def parseSymbols(self, lief_binary, base_addr=None):
67
74
  active_base = self._resolve_base_addr(lief_binary, base_addr)
68
- # find VA of first code section
69
75
  function_symbols = {}
70
- code_base_address = None
71
- for section in lief_binary.sections:
72
- if section.characteristics & 0x20000000:
73
- code_base_address = active_base + section.virtual_address
74
- break
75
- if code_base_address is None:
76
- return function_symbols
77
76
  for symbol in lief_binary.symbols:
78
77
  if hasattr(symbol.complex_type, "name") and symbol.complex_type.name == "FUNCTION":
78
+ if symbol.section is None:
79
+ # section_idx 0/-1/-2 (undefined-external/absolute/debug): not a locally
80
+ # defined function, its value is not a usable in-image offset.
81
+ continue
79
82
  function_name = ""
80
83
  with contextlib.suppress(UnicodeDecodeError, AttributeError):
81
84
  # here may occur a LIEF exception that we want to skip ->
82
85
  # UnicodeDecodeError: 'utf-32-le' codec can't decode bytes in position 0-3: code point not in range(0x110000)
83
86
  function_name = symbol.name
84
87
  if function_name and all(ord(c) in range(0x20, 0x7F) for c in function_name):
85
- # for some reason, we need to add the section_offset of .text here
86
- function_offset = code_base_address + symbol.value
88
+ function_offset = active_base + symbol.section.virtual_address + symbol.value
87
89
  if function_offset not in function_symbols:
88
90
  function_symbols[function_offset] = function_name
89
91
  return function_symbols
@@ -143,7 +143,12 @@ class RustSymbolProvider(AbstractLabelProvider):
143
143
  """Process PE binary symbols for Rust demangling."""
144
144
  active_base = resolve_pe_base_addr(lief_binary, base_addr)
145
145
  # Parse PE exports
146
- for function in lief_binary.exported_functions:
146
+ export = lief_binary.get_export()
147
+ for function in export.entries if export is not None else []:
148
+ if function.is_extern or function.is_forwarded:
149
+ # forwarder/extern entries redirect to another module's export and have no
150
+ # local address (LIEF sets .address to 0 for them) - not a local function.
151
+ continue
147
152
  try:
148
153
  try:
149
154
  raw_name = function.name
@@ -158,21 +163,16 @@ class RustSymbolProvider(AbstractLabelProvider):
158
163
  except _DEMANGLE_ERRORS as exc:
159
164
  LOGGER.debug("Failed to demangle Rust symbol %s: %s", function.name, exc)
160
165
 
161
- code_base_address = None
162
- for section in lief_binary.sections:
163
- if section.characteristics & 0x20000000:
164
- code_base_address = active_base + section.virtual_address
165
- break
166
- if code_base_address is None:
167
- return
168
166
  # working example: 3969e1a88a063155a6f61b0ca1ac33114c1a39151f3c7dd019084abd30553eab
169
167
  # Parse PE symbols (COFF) if available and LIEF extracted them
170
168
  # (Similar logic to PeSymbolProvider but focusing on Rust)
171
169
  for symbol in lief_binary.symbols:
172
170
  # Check if it is a function symbol and has a section
173
- if (
174
- hasattr(symbol.complex_type, "name") and symbol.complex_type.name == "FUNCTION"
175
- ): # and symbol.has_section:
171
+ if hasattr(symbol.complex_type, "name") and symbol.complex_type.name == "FUNCTION":
172
+ if symbol.section is None:
173
+ # section_idx 0/-1/-2 (undefined-external/absolute/debug): not a locally
174
+ # defined function, its value is not a usable in-image offset.
175
+ continue
176
176
  try:
177
177
  try:
178
178
  raw_name = symbol.name
@@ -182,7 +182,7 @@ class RustSymbolProvider(AbstractLabelProvider):
182
182
  demangled = demangle(raw_name)
183
183
  if demangled:
184
184
  demangled = remove_bad_spaces(demangled)
185
- function_offset = code_base_address + symbol.value
185
+ function_offset = active_base + symbol.section.virtual_address + symbol.value
186
186
  if function_offset not in self._func_symbols:
187
187
  self._func_symbols[function_offset] = demangled
188
188
  except _DEMANGLE_ERRORS as exc:
@@ -744,12 +744,26 @@ class FunctionCandidateManager:
744
744
  if section_name == ".pdata":
745
745
  rva_start = section_va_start - self.disassembly.binary_info.base_addr
746
746
  rva_end = section_va_end - self.disassembly.binary_info.base_addr
747
- # .pdata entries are 12 bytes long (3 DWORDs)
747
+ # .pdata entries are 12 bytes long (3 DWORDs): BeginAddress, EndAddress, UnwindInfoAddress
748
748
  for offset in range(rva_start, rva_end - 11, 12):
749
- packed_dword = self.disassembly.binary_info.binary[offset : offset + 4]
750
- if len(packed_dword) < 4:
749
+ packed_entry = self.disassembly.getRawBytes(offset, 12)
750
+ if len(packed_entry) < 12:
751
751
  break
752
- rva_function_candidate = struct.unpack("I", packed_dword)[0]
752
+ rva_function_candidate, _rva_function_end, rva_unwind_info = struct.unpack("<III", packed_entry)
753
753
  if rva_function_candidate == 0:
754
754
  break
755
+ if self._isChainedUnwindInfo(rva_unwind_info):
756
+ # UNW_FLAG_CHAININFO: this entry is a secondary fragment (e.g. a
757
+ # split-off cold/epilogue chunk) chained to another function's
758
+ # primary RUNTIME_FUNCTION entry, not an independent function start.
759
+ continue
755
760
  self.addExceptionCandidate(self.disassembly.binary_info.base_addr + rva_function_candidate)
761
+
762
+ def _isChainedUnwindInfo(self, rva_unwind_info):
763
+ # x64 UNWIND_INFO header byte 0 packs Version (bits 0-2) and Flags (bits 3-7);
764
+ # UNW_FLAG_CHAININFO (0x4) means this RUNTIME_FUNCTION entry chains to another
765
+ # function's primary entry instead of describing an independent function.
766
+ header_byte = self.disassembly.getRawBytes(rva_unwind_info, 1)
767
+ if not header_byte:
768
+ return False
769
+ return bool((header_byte[0] >> 3) & 0x4)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smda
3
- Version: 4.2.2
3
+ Version: 4.2.3
4
4
  Summary: A recursive disassmbler optimized for CFG recovery from memory dumps. Based on capstone.
5
5
  Author-email: Daniel Plohmann <daniel.plohmann@mailbox.org>
6
6
  License: BSD 2-Clause
@@ -146,6 +146,7 @@ make test
146
146
 
147
147
  ## Version History
148
148
 
149
+ * 2026-07-10: v4.2.3 - Fix: function promotion bug caused by missing symbol type evaluation (THX: @r0ny123).
149
150
  * 2026-07-09: v4.2.2 - Now also parsing delay import tables from Windows PEs.
150
151
  * 2026-07-09: v4.2.1 - Better detection of CFG instructions with prefixes, improved accuracy of gap search. (THX: @r0ny123) IDA ARM64 export.
151
152
  * 2026-06-24: v4.2.0 - Further improvements for inter-procedural operand escaping (closer to x86_x64 and traditional PIC hashing).
@@ -81,6 +81,34 @@ class PdataExtractionTestSuite(unittest.TestCase):
81
81
  self.assertNotIn(BASE_ADDR, candidates)
82
82
  self.assertEqual(len(candidates), 1)
83
83
 
84
+ def test_pdata_chained_unwind_info_excluded(self):
85
+ config = SmdaConfig()
86
+ fcm = FunctionCandidateManager(config)
87
+
88
+ # Entry format: Start RVA, End RVA, Unwind RVA
89
+ entries = [
90
+ (0x2000, 0x2040, 0x3000), # primary entry, unwind info at 0x3000 (Flags=0)
91
+ (0x2050, 0x2090, 0x3010), # chained entry, unwind info at 0x3010 (UNW_FLAG_CHAININFO set)
92
+ ]
93
+
94
+ pdata_bytes = b"".join(struct.pack("III", *entry) for entry in entries)
95
+
96
+ binary = bytearray(BINARY_SIZE)
97
+ binary[PDATA_OFFSET : PDATA_OFFSET + len(pdata_bytes)] = pdata_bytes
98
+ # UNWIND_INFO header byte 0 packs Version (bits 0-2) and Flags (bits 3-7); 0x20 sets
99
+ # UNW_FLAG_CHAININFO (0x4) with Version 0.
100
+ binary[0x3010] = 0x20
101
+
102
+ disasm = DisassemblyResult()
103
+ disasm.binary_info = MockBinaryInfo(64, BASE_ADDR, bytes(binary), pdata_size=len(pdata_bytes))
104
+
105
+ fcm.init(disasm)
106
+
107
+ candidates = fcm.candidates
108
+ self.assertIn(BASE_ADDR + 0x2000, candidates)
109
+ self.assertNotIn(BASE_ADDR + 0x2050, candidates)
110
+ self.assertEqual(len(candidates), 1)
111
+
84
112
  def test_pdata_misaligned_size(self):
85
113
  config = SmdaConfig()
86
114
  fcm = FunctionCandidateManager(config)
@@ -36,9 +36,11 @@ class _MockDelayImport:
36
36
 
37
37
 
38
38
  class _MockExport:
39
- def __init__(self, name, address):
39
+ def __init__(self, name, address, *, is_extern=False, is_forwarded=False):
40
40
  self.name = name
41
41
  self.address = address
42
+ self.is_extern = is_extern
43
+ self.is_forwarded = is_forwarded
42
44
 
43
45
 
44
46
  class _MockSection:
@@ -48,10 +50,11 @@ class _MockSection:
48
50
 
49
51
 
50
52
  class _MockSymbol:
51
- def __init__(self, name, value):
53
+ def __init__(self, name, value, *, section=None):
52
54
  self.name = name
53
55
  self.value = value
54
56
  self.complex_type = SimpleNamespace(name="FUNCTION")
57
+ self.section = section
55
58
 
56
59
 
57
60
  class _MockPeBinary:
@@ -76,6 +79,11 @@ class _MockPeBinary:
76
79
  self.has_delay_imports = bool(self.delay_imports)
77
80
  self.has_imports = bool(self.imports)
78
81
 
82
+ def get_export(self):
83
+ if not self.exported_functions:
84
+ return None
85
+ return SimpleNamespace(entries=self.exported_functions)
86
+
79
87
 
80
88
  class TestPeSymbolProviderImports(unittest.TestCase):
81
89
  def test_parse_imports_uses_active_base_addr_not_imagebase(self):
@@ -182,7 +190,7 @@ class TestPeSymbolProviderMetadata(unittest.TestCase):
182
190
  pe_binary = _MockPeBinary(
183
191
  exported_functions=[_MockExport("exported_func", 0x1000)],
184
192
  sections=[_MockSection(0x20000000, 0x1000)],
185
- symbols=[_MockSymbol("local_func", 0x200)],
193
+ symbols=[_MockSymbol("local_func", 0x200, section=_MockSection(0x20000000, 0x1000))],
186
194
  imagebase=0x140000000,
187
195
  )
188
196
  symbols = provider.collectSymbols(pe_binary, base_addr=0x400000)
@@ -194,7 +202,7 @@ class TestPeSymbolProviderMetadata(unittest.TestCase):
194
202
  pe_binary = _MockPeBinary(
195
203
  exported_functions=[_MockExport("exported_func", 0x1000)],
196
204
  sections=[_MockSection(0x20000000, 0x1000)],
197
- symbols=[_MockSymbol("local_func", 0x200)],
205
+ symbols=[_MockSymbol("local_func", 0x200, section=_MockSection(0x20000000, 0x1000))],
198
206
  imagebase=0x140000000,
199
207
  )
200
208
  symbols = provider.collectSymbols(pe_binary, base_addr=0)
@@ -227,7 +235,7 @@ class TestPeSymbolProviderMetadata(unittest.TestCase):
227
235
  imports=[_MockImportLibrary("KERNEL32.dll", [_MockImportEntry("CreateFileW", 0x3000)])],
228
236
  exported_functions=[_MockExport("exported_func", 0x1000)],
229
237
  sections=[_MockSection(0x20000000, 0x1000)],
230
- symbols=[_MockSymbol("local_func", 0x200)],
238
+ symbols=[_MockSymbol("local_func", 0x200, section=_MockSection(0x20000000, 0x1000))],
231
239
  imagebase=0x140000000,
232
240
  )
233
241
  binary_info = BinaryInfo(b"")
@@ -254,7 +262,7 @@ class TestPeSymbolProviderMetadata(unittest.TestCase):
254
262
  pe_binary = _MockPeBinary(
255
263
  exported_functions=[_MockExport("exported_func", 0x1000)],
256
264
  sections=[_MockSection(0x20000000, 0x1000)],
257
- symbols=[_MockSymbol("local_func", 0x200)],
265
+ symbols=[_MockSymbol("local_func", 0x200, section=_MockSection(0x20000000, 0x1000))],
258
266
  imagebase=0x140000000,
259
267
  )
260
268
  binary_info = BinaryInfo(b"")
@@ -267,6 +275,32 @@ class TestPeSymbolProviderMetadata(unittest.TestCase):
267
275
  self.assertEqual(symbols[0x401000], "exported_func")
268
276
  self.assertEqual(symbols[0x401200], "local_func")
269
277
 
278
+ def test_parse_exports_skips_forwarded_and_extern_entries(self):
279
+ provider = PeSymbolProvider(None)
280
+ pe_binary = _MockPeBinary(
281
+ exported_functions=[
282
+ _MockExport("real_func", 0x1000),
283
+ _MockExport("forwarded_func", 0, is_forwarded=True),
284
+ _MockExport("extern_func", 0, is_extern=True),
285
+ ],
286
+ imagebase=0x140000000,
287
+ )
288
+ symbols = provider.parseExports(pe_binary, base_addr=0x400000)
289
+ self.assertEqual(symbols, {0x401000: "real_func"})
290
+
291
+ def test_parse_symbols_skips_entries_with_no_section(self):
292
+ provider = PeSymbolProvider(None)
293
+ pe_binary = _MockPeBinary(
294
+ sections=[_MockSection(0x20000000, 0x1000)],
295
+ symbols=[
296
+ _MockSymbol("local_func", 0x200, section=_MockSection(0x20000000, 0x1000)),
297
+ _MockSymbol("undefined_func", 0, section=None),
298
+ ],
299
+ imagebase=0x140000000,
300
+ )
301
+ symbols = provider.parseSymbols(pe_binary, base_addr=0x400000)
302
+ self.assertEqual(symbols, {0x401200: "local_func"})
303
+
270
304
 
271
305
  if __name__ == "__main__":
272
306
  unittest.main()
@@ -18,12 +18,13 @@ from smda.common.labelprovider.RustSymbolProvider import RustSymbolProvider
18
18
 
19
19
 
20
20
  class MockSymbol:
21
- def __init__(self, name, value, is_function=True, demangled_name=None):
21
+ def __init__(self, name, value, is_function=True, demangled_name=None, section=None):
22
22
  self.name = name
23
23
  self.value = value
24
24
  self.is_function = is_function
25
25
  self._demangled_name = demangled_name
26
26
  self.complex_type = type("obj", (object,), {"name": "FUNCTION"})
27
+ self.section = section
27
28
 
28
29
  @property
29
30
  def demangled_name(self):
@@ -44,6 +45,11 @@ class MockLiefBinary:
44
45
  self.symbols = symbols
45
46
  self.imports = []
46
47
 
48
+ def get_export(self):
49
+ if not self.exported_functions:
50
+ return None
51
+ return type("obj", (object,), {"entries": self.exported_functions})()
52
+
47
53
 
48
54
  class MockSection:
49
55
  def __init__(self, characteristics, virtual_address):
@@ -52,9 +58,11 @@ class MockSection:
52
58
 
53
59
 
54
60
  class MockExport:
55
- def __init__(self, name, address):
61
+ def __init__(self, name, address, is_extern=False, is_forwarded=False):
56
62
  self.name = name
57
63
  self.address = address
64
+ self.is_extern = is_extern
65
+ self.is_forwarded = is_forwarded
58
66
 
59
67
 
60
68
  class TestRustDemangler(unittest.TestCase):
@@ -240,7 +248,7 @@ class TestRustSymbolProvider(unittest.TestCase):
240
248
  def test_pe_rust_symbols_use_base_addr_not_imagebase(self):
241
249
  provider = RustSymbolProvider(None)
242
250
  mock_binary = MockLiefBinary(
243
- [MockSymbol("_ZN3foo3barE", 0x200)],
251
+ [MockSymbol("_ZN3foo3barE", 0x200, section=MockSection(0x20000000, 0x1000))],
244
252
  exported_functions=[MockExport("_RNvC6_123foo3bar", 0x1000)],
245
253
  )
246
254
  mock_binary.imagebase = 0x140000000
@@ -254,6 +262,29 @@ class TestRustSymbolProvider(unittest.TestCase):
254
262
  self.assertNotIn(0x140001000, provider.getFunctionSymbols())
255
263
  self.assertNotIn(0x140001200, provider.getFunctionSymbols())
256
264
 
265
+ def test_pe_rust_symbols_skip_forwarded_exports_and_sectionless_symbols(self):
266
+ provider = RustSymbolProvider(None)
267
+ mock_binary = MockLiefBinary(
268
+ [
269
+ MockSymbol("_ZN3foo3barE", 0x200, section=MockSection(0x20000000, 0x1000)),
270
+ MockSymbol("_ZN3foo3bazE", 0, section=None),
271
+ ],
272
+ exported_functions=[
273
+ MockExport("_RNvC6_123foo3bar", 0x1000),
274
+ MockExport("_RNvC6_123foo3qux", 0, is_forwarded=True),
275
+ ],
276
+ )
277
+ mock_binary.imagebase = 0x140000000
278
+ mock_binary.sections = [MockSection(0x20000000, 0x1000)]
279
+
280
+ with mock.patch("lief.PE.Binary", MockLiefBinary):
281
+ provider._update_pe(mock_binary, base_addr=0x400000)
282
+
283
+ self.assertEqual(provider.getSymbol(0x401000), "123foo::bar")
284
+ self.assertEqual(provider.getSymbol(0x401200), "foo::bar")
285
+ self.assertNotIn(0x400000, provider.getFunctionSymbols())
286
+ self.assertEqual(len(provider.getFunctionSymbols()), 2)
287
+
257
288
  def test_detection_logic(self):
258
289
  """Test Rust binary detection based on signatures."""
259
290
  provider = RustSymbolProvider(None)
@@ -1 +0,0 @@
1
- __version__ = "4.2.2"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes