1''' 2PROJECT: ReactOS baseaddress updater 3LICENSE: MIT (https://spdx.org/licenses/MIT) 4PURPOSE: Update baseaddresses of all modules 5COPYRIGHT: Copyright 2017,2018 Mark Jansen (mark.jansen@reactos.org) 6''' 7 8from __future__ import print_function, absolute_import, division 9 10USAGE = """ 11This script will update the baseaddresses of all modules, based on the build output. 12 13Specify the build output dir as commandline argument to the script: 14`python gen_baseaddress.py C:\\Users\\Mark\\reactos\\output-MinGW-i386` 15 16Multiple directories can be specified: 17`python gen_baseaddress r:/build/msvc r:/build/gcc` 18 19Specify -64 as first argument to use 64-bit addresses: 20`python gen_baseaddress -64 r:\\build\\msvc-x64` 21""" 22 23import os 24import struct 25import sys 26 27try: 28 import pefile 29except ImportError: 30 print('# Please install pefile from pip or https://github.com/erocarrera/pefile') 31 sys.exit(-1) 32 33 34ALL_EXTENSIONS = ( 35 '.dll', '.acm', '.ax', '.cpl', '.drv', '.ocx', '.ime' 36) 37 38PRIORITIES = ( 39 'ntdll.dll', 40 'kernel32.dll', 41 'msvcrt.dll', 42 'advapi32.dll', 43 'gdi32.dll', 44 'user32.dll', 45 'dhcpcsvc.dll', 46 'dnsapi.dll', 47 'icmp.dll', 48 'iphlpapi.dll', 49 'ws2_32.dll', 50 'ws2help.dll', 51 'shlwapi.dll', 52 'rpcrt4.dll', 53 'comctl32.dll', 54 'ole32.dll', 55 'winspool.drv', 56 'winmm.dll', 57 'comdlg32.dll', 58 'shell32.dll', 59 'lz32.dll', 60 'version.dll', 61 'oleaut32.dll', 62 'setupapi.dll', 63 'mpr.dll', 64 'crypt32.dll', 65 'wininet.dll', 66 'urlmon.dll', 67 'psapi.dll', 68 'imm32.dll', 69 'msvfw32.dll', 70 'dbghelp.dll', 71 'devmgr.dll', 72 'msacm32.dll', 73 'netapi32.dll', 74 'powrprof.dll', 75 'secur32.dll', 76 'wintrust.dll', 77 'avicap32.dll', 78 'cabinet.dll', 79 'dsound.dll', 80 'glu32.dll', 81 'opengl32.dll', 82 'riched20.dll', 83 'userenv.dll', 84 'uxtheme.dll', 85 'cryptui.dll', 86 'csrsrv.dll', 87 'basesrv.dll', 88 'winsrv.dll', 89 'dplayx.dll', 90 'gdiplus.dll', 91 'msimg32.dll', 92 'mswsock.dll', 93 'oledlg.dll', 94 'rasapi32.dll', 95 'rsaenh.dll', 96 'samlib.dll', 97 'sensapi.dll', 98 'sfc_os.dll', 99 'snmpapi.dll', 100 'spoolss.dll', 101 'usp10.dll', 102) 103 104EXCLUDE = ( 105 'bmfd.dll', 106 'bootvid.dll', 107 'framebuf.dll', 108 'ftfd.dll', 109 'genincdata.dll', 110 'hal.dll', 111 'halaacpi.dll', 112 'halacpi.dll', 113 'halapic.dll', 114 'kbda1.dll', 115 'kbda2.dll', 116 'kbda3.dll', 117 'kbdal.dll', 118 'kbdarme.dll', 119 'kbdarmw.dll', 120 'kbdaze.dll', 121 'kbdazel.dll', 122 'kbdbe.dll', 123 'kbdbga.dll', 124 'kbdbgm.dll', 125 'kbdbgt.dll', 126 'kbdblr.dll', 127 'kbdbr.dll', 128 'kbdbu.dll', 129 'kbdbur.dll', 130 'kbdcan.dll', 131 'kbdcr.dll', 132 'kbdcz.dll', 133 'kbdcz1.dll', 134 'kbdda.dll', 135 'kbddv.dll', 136 'kbdeo.dll', 137 'kbdes.dll', 138 'kbdest.dll', 139 'kbdfc.dll', 140 'kbdfi.dll', 141 'kbdfr.dll', 142 'kbdgeo.dll', 143 'kbdgerg.dll', 144 'kbdgneo.dll', 145 'kbdgr.dll', 146 'kbdgr1.dll', 147 'kbdgrist.dll', 148 'kbdhe.dll', 149 'kbdheb.dll', 150 'kbdhu.dll', 151 'kbdic.dll', 152 'kbdinasa.dll', 153 'kbdinben.dll', 154 'kbdindev.dll', 155 'kbdinguj.dll', 156 'kbdinmal.dll', 157 'kbdir.dll', 158 'kbdit.dll', 159 'kbdja.dll', 160 'kbdjpn.dll', 161 'kbdkaz.dll', 162 'kbdko.dll', 163 'kbdkor.dll', 164 'kbdla.dll', 165 'kbdlt1.dll', 166 'kbdlv.dll', 167 'kbdmac.dll', 168 'kbdne.dll', 169 'kbdno.dll', 170 'kbdpl.dll', 171 'kbdpl1.dll', 172 'kbdpo.dll', 173 'kbdro.dll', 174 'kbdrost.dll', 175 'kbdru.dll', 176 'kbdru1.dll', 177 'kbdsf.dll', 178 'kbdsg.dll', 179 'kbdsk.dll', 180 'kbdsk1.dll', 181 'kbdsl.dll', 182 'kbdsl1.dll', 183 'kbdsp.dll', 184 'kbdsw.dll', 185 'kbdtat.dll', 186 'kbdth0.dll', 187 'kbdth1.dll', 188 'kbdth2.dll', 189 'kbdth3.dll', 190 'kbdtuf.dll', 191 'kbdtuq.dll', 192 'kbduk.dll', 193 'kbdur.dll', 194 'kbdurs.dll', 195 'kbdus.dll', 196 'kbdusa.dll', 197 'kbdusl.dll', 198 'kbdusr.dll', 199 'kbdusx.dll', 200 'kbduzb.dll', 201 'kbdvntc.dll', 202 'kbdycc.dll', 203 'kbdycl.dll', 204 'kdcom.dll', 205 'kdvbox.dll', 206 'vgaddi.dll', 207 'dllexport_test_dll1.dll', 208 'dllexport_test_dll2.dll', 209 'dllimport_test.dll', 210 'localspl_apitest.dll', 211 'MyEventProvider.dll', 212 'redirtest1.dll', 213 'redirtest2.dll', 214 'win32u_2k3sp2.dll', 215 'win32u_vista.dll', 216 'win32u_xpsp2.dll', 217 'testvdd.dll', 218) 219 220IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b 221IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b 222 223IMAGE_TYPES = { 224 IMAGE_NT_OPTIONAL_HDR32_MAGIC: 0, 225 IMAGE_NT_OPTIONAL_HDR64_MAGIC: 0 226} 227 228IS_64_BIT = False 229 230def size_of_image(filename): 231 if IS_64_BIT: 232 return 0xFE0000 # This results in 0x1000000 / 16MB space 233 with open(filename, 'rb') as fin: 234 if fin.read(2) != b'MZ': 235 print(filename, 'No dos header found!') 236 return 0 237 fin.seek(0x3C) 238 e_lfanew = struct.unpack('i', fin.read(4))[0] 239 fin.seek(e_lfanew) 240 if fin.read(4) != b'PE\0\0': 241 print(filename, 'No PE header found!') 242 return 0 243 fin.seek(e_lfanew + 0x18) 244 pe_magic = struct.unpack('h', fin.read(2))[0] 245 if pe_magic in IMAGE_TYPES.keys(): 246 IMAGE_TYPES[pe_magic] += 1 247 fin.seek(e_lfanew + 0x50) 248 pe_size_of_image = struct.unpack('i', fin.read(4))[0] 249 return pe_size_of_image 250 print(filename, 'Unknown executable format!') 251 return 0 252 253 254class Module(object): 255 def __init__(self, name, address, size, filename): 256 self._name = name 257 self.address = address 258 self.size = size 259 self._reserved = address != 0 260 self.filename = filename 261 262 def gen_baseaddress(self, output_file): 263 name, ext = os.path.splitext(self._name) 264 postfix = '' 265 if ext in('.acm', '.drv') and self._name != 'winspool.drv': 266 name = self._name 267 if name == 'ntdll': 268 postfix = ' # should be above 0x%08x' % self.address 269 elif self._reserved: 270 postfix = ' # reserved' 271 output_file.write('set(baseaddress_%-30s 0x%08x)%s\n' % (name, self.address, postfix)) 272 273 def end(self): 274 return self.address + self.size 275 276 def __repr__(self): 277 return '%s (0x%08x - 0x%08x)' % (self._name, self.address, self.end()) 278 279class MemoryLayout(object): 280 def __init__(self, startaddress): 281 self.addresses = [] 282 self.found = {} 283 self.reserved = {} 284 self.initial = startaddress 285 self.start_at = 0 286 self.module_padding = 0x2000 287 288 def add_reserved(self, name, address): 289 self.reserved[name] = (address, 0) 290 291 def add(self, filename, name): 292 size = size_of_image(filename) 293 addr = 0 294 if name in self.found: 295 return # Assume duplicate files (rshell, ...) are 1:1 copies 296 if name in self.reserved: 297 addr = self.reserved[name][0] 298 self.reserved[name] = (addr, size) 299 self.found[name] = Module(name, addr, size, filename) 300 301 def _next_address(self, size): 302 if self.start_at: 303 addr = (self.start_at - size - self.module_padding - 0xffff) & 0xffffffffffff0000 304 self.start_at = addr 305 else: 306 addr = self.start_at = self.initial 307 return addr 308 309 def next_address(self, size): 310 while True: 311 current_start = self._next_address(size) 312 current_end = current_start + size + self.module_padding 313 # Is there overlap with reserved modules? 314 for key, reserved in self.reserved.items(): 315 res_start = reserved[0] 316 res_end = res_start + reserved[1] + self.module_padding 317 if (res_start <= current_start <= res_end) or \ 318 (res_start <= current_end <= res_end) or \ 319 (current_start < res_start and current_end > res_end): 320 # We passed this reserved item, we can remove it now 321 self.start_at = min(res_start, current_start) 322 del self.reserved[key] 323 current_start = 0 324 break 325 # No overlap with a reserved module? 326 if current_start: 327 return current_start 328 329 def update(self, priorities): 330 # sort addresses, should only contain reserved modules at this point! 331 for key, reserved in self.reserved.items(): 332 assert reserved[1] != 0, key 333 for curr in priorities: 334 if not curr in self.found: 335 print('# Did not find', curr, '!') 336 else: 337 obj = self.found[curr] 338 del self.found[curr] 339 if not obj.address: 340 obj.address = self.next_address(obj.size) 341 self.addresses.append(obj) 342 # We handled all known modules now, run over the rest we found 343 for key in sorted(self.found): 344 obj = self.found[key] 345 obj.address = self.next_address(obj.size) 346 self.addresses.append(obj) 347 348 def gen_baseaddress(self, output_file): 349 for obj in self.addresses: 350 obj.gen_baseaddress(output_file) 351 352def get_target_file(ntdll_path): 353 if 'pefile' in globals(): 354 ntdll_pe = pefile.PE(ntdll_path, fast_load=True) 355 names = [sect.Name.strip(b'\0') for sect in ntdll_pe.sections] 356 count = b'|'.join(names).count(b'/') 357 if IS_64_BIT: 358 return 'baseaddress64.cmake' 359 elif b'.rossym' in names: 360 return 'baseaddress.cmake' 361 elif count == 0: 362 return 'baseaddress_msvc.cmake' 363 elif count > 3: 364 return 'baseaddress_dwarf.cmake' 365 else: 366 assert False, "Unknown" 367 return None 368 369def run_dir(target): 370 if IS_64_BIT: 371 layout = MemoryLayout(0x7FFB7000000) 372 else: 373 layout = MemoryLayout(0x7c920000) 374 layout.add_reserved('user32.dll', 0x77a20000) 375 IMAGE_TYPES[IMAGE_NT_OPTIONAL_HDR64_MAGIC] = 0 376 IMAGE_TYPES[IMAGE_NT_OPTIONAL_HDR32_MAGIC] = 0 377 for root, _, files in os.walk(target): 378 for dll in [filename for filename in files if filename.endswith(ALL_EXTENSIONS)]: 379 if not dll in EXCLUDE and not dll.startswith('api-ms-win-'): 380 layout.add(os.path.join(root, dll), dll) 381 ntdll_path = layout.found['ntdll.dll'].filename 382 target_file = get_target_file(ntdll_path) 383 if target_file: 384 target_dir = os.path.realpath(os.path.dirname(os.path.dirname(__file__))) 385 target_path = os.path.join(target_dir, 'cmake', target_file) 386 output_file = open(target_path, "w") 387 else: 388 output_file = sys.stdout 389 with output_file: 390 output_file.write('# Generated from {}\n'.format(target)) 391 output_file.write('# Generated by sdk/tools/gen_baseaddress.py\n\n') 392 layout.update(PRIORITIES) 393 layout.gen_baseaddress(output_file) 394 395def main(): 396 dirs = sys.argv[1:] 397 398 if len(dirs) > 0 and dirs[0] == '-64': 399 print('Using 64-bit addresses') 400 global IS_64_BIT 401 IS_64_BIT = True 402 dirs = sys.argv[2:] 403 if len(dirs) < 1: 404 trydir = os.getcwd() 405 print(USAGE) 406 print('No path specified, trying the working directory: ', trydir) 407 dirs = [trydir] 408 for onedir in dirs: 409 if onedir.lower() in ['-help', '/help', '/h', '-h', '/?', '-?']: 410 print(USAGE) 411 else: 412 run_dir(onedir) 413 414 415if __name__ == '__main__': 416 main() 417