path_properties.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. #!/usr/bin/env python3
  2. import os
  3. import signal
  4. from shell_helpers import LF
  5. class PathProperties:
  6. default_c_std = 'c11'
  7. default_cxx_std = 'c++17'
  8. # All new properties must be listed here or else you get an error.
  9. default_properties = {
  10. 'allowed_archs': None,
  11. # The example uses aarch32 instructions which are not present in ARMv7.
  12. # Therefore, it cannot be run in baremetal ARMv7 CPUs.
  13. # User mode simulation however seems to enable aarch32 so these run fine.
  14. 'arm_aarch32': False,
  15. # Examples that can be built in baremetal.
  16. 'baremetal': False,
  17. 'c_std': default_c_std,
  18. 'cc_flags': [
  19. '-Wall', LF,
  20. '-Werror', LF,
  21. '-Wextra', LF,
  22. '-Wno-unused-function', LF,
  23. '-ggdb3', LF,
  24. # PIE causes the following problems:
  25. # * QEMU GDB step debug does not find breakpoints:
  26. # https://stackoverflow.com/questions/51310756/how-to-gdb-step-debug-a-dynamically-linked-executable-in-qemu-user-mode/51343326#51343326
  27. # * when writing assembly code, we have to constantly think about it:
  28. # https://stackoverflow.com/questions/2463150/what-is-the-fpie-option-for-position-independent-executables-in-gcc-and-ld/51308031#51308031
  29. # As of 91986fb2955f96e06d1c5ffcc5536ba9f0af1fd9, our Buildroot toolchain
  30. # does not have it enabled by default, but the Ubuntu 18.04 host toolchain does.
  31. '-fno-pie', LF,
  32. '-no-pie', LF,
  33. ],
  34. 'cc_flags_after': ['-lm', LF],
  35. 'cc_pedantic': True,
  36. 'cxx_std': default_cxx_std,
  37. 'disrupts_system': False,
  38. # Expected program exit status. When signals are raised, this refers
  39. # to the native exit status. as reported by Bash #?.
  40. 'exit_status': 0,
  41. 'extra_objs': [],
  42. # Explicitly don't add the baremetal bootloader object which normally gets automatically
  43. # added to baremetal examples.
  44. 'extra_objs_disable_baremetal_bootloader': False,
  45. # We should get rid of this if we ever properly implement dependency graphs.
  46. 'extra_objs_lkmc_common': False,
  47. 'gem5_unimplemented_instruction': False,
  48. 'interactive': False,
  49. # The script takes a perceptible amount of time to run. Possibly an infinite loop.
  50. 'more_than_1s': False,
  51. # The path should not be built. E.g., it is symlinked into multiple archs.
  52. 'no_build': False,
  53. # The path does not generate an executable in itself, e.g.
  54. # it only generates intermediate object files. Therefore it
  55. # should not be run while testing.
  56. 'no_executable': False,
  57. # The script requires a non-trivial argument to be passed to run properly.
  58. 'requires_argument': False,
  59. 'requires_dynamic_library': False,
  60. 'requires_m5ops': False,
  61. # gem5 fatal: syscall getcpu (#168) unimplemented.
  62. 'requires_syscall_getcpu': False,
  63. 'requires_semihosting': False,
  64. # Requires certain of our custom kernel modules to be inserted to run.
  65. 'requires_kernel_modules': False,
  66. # The example requires sudo, which usually implies that it can do something
  67. # deeply to the system it runs on, which would preventing further interactive
  68. # or test usage of the system, for example poweroff or messing up the GUI.
  69. 'requires_sudo': False,
  70. # The signal received is generated by the OS implicitly rather than explicitly
  71. # done sith signal(), e.g. Illegal instruction or sigsegv.
  72. # Therefore, it won't behave in the same way in baremetal. aarch64 already
  73. # has an exception handler which we could use but arm doesn't and the IP
  74. # goes astray, so let's just skip this for now.
  75. 'signal_generated_by_os': False,
  76. 'signal_received': None,
  77. # We were lazy to properly classify why we are skipping these tests.
  78. # TODO get it done.
  79. 'skip_run_unclassified': False,
  80. # Aruments added automatically to run when running tests,
  81. # but not on manual running.
  82. 'test_run_args': {},
  83. # Examples that can be built in userland.
  84. 'userland': False,
  85. }
  86. '''
  87. Encodes properties of userland and baremetal paths.
  88. For directories, it applies to all files under the directory.
  89. Used to determine how to build and test the examples.
  90. '''
  91. def __init__(
  92. self,
  93. properties
  94. ):
  95. for key in properties:
  96. if not key in self.default_properties:
  97. raise ValueError('Unknown key: {}'.format(key))
  98. self.properties = properties.copy()
  99. def __getitem__(self, key):
  100. return self.properties[key]
  101. def __repr__(self):
  102. return str(self.properties)
  103. def set_path_components(self, path_components):
  104. self.path_components = path_components
  105. def should_be_built(
  106. self,
  107. env,
  108. link=False,
  109. ):
  110. ext = os.path.splitext(self.path_components[-1])[1]
  111. return (
  112. not (
  113. len(self.path_components) > 1 and \
  114. self.path_components[1] == 'libs' and \
  115. not env['package_all'] and \
  116. not self.path_components[2] in env['package']
  117. ) and
  118. not self['no_build'] and
  119. (
  120. self['allowed_archs'] is None or
  121. env['arch'] in self['allowed_archs']
  122. ) and
  123. (
  124. (
  125. env['mode'] == 'userland' and
  126. (
  127. self['userland'] and
  128. ext in env['build_in_exts']
  129. )
  130. ) or
  131. (
  132. env['mode'] == 'baremetal' and (
  133. self['baremetal'] and
  134. ext in env['baremetal_build_in_exts']
  135. )
  136. )
  137. ) and
  138. not (
  139. link and
  140. self['no_executable']
  141. )
  142. )
  143. def should_be_tested(self, env):
  144. return (
  145. self.should_be_built(
  146. env,
  147. ) and
  148. not (
  149. env['mode'] == 'baremetal' and (
  150. self['arm_aarch32'] or
  151. self['signal_generated_by_os']
  152. )
  153. ) and
  154. not self['disrupts_system'] and
  155. not self['interactive'] and
  156. not self['more_than_1s'] and
  157. not self['no_executable'] and
  158. not self['requires_argument'] and
  159. not self['requires_kernel_modules'] and
  160. not self['requires_sudo'] and
  161. not self['skip_run_unclassified'] and
  162. not (
  163. env['emulator'] == 'gem5' and
  164. (
  165. self['gem5_unimplemented_instruction'] or
  166. # gem5 does not report signals properly.
  167. self['signal_received'] is not None or
  168. self['requires_dynamic_library'] or
  169. self['requires_semihosting'] or
  170. self['requires_syscall_getcpu']
  171. )
  172. ) and
  173. not (
  174. env['emulator'] == 'qemu' and
  175. (
  176. self['requires_m5ops']
  177. )
  178. )
  179. )
  180. def _update_dict(self, other_tmp_properties, key):
  181. if key in self.properties and key in other_tmp_properties:
  182. other_tmp_properties[key] = {
  183. **self.properties[key],
  184. **other_tmp_properties[key]
  185. }
  186. def _update_list(self, other_tmp_properties, key):
  187. if key in self.properties and key in other_tmp_properties:
  188. other_tmp_properties[key] = \
  189. self.properties[key] + \
  190. other_tmp_properties[key]
  191. def update(self, other):
  192. other_tmp_properties = other.properties.copy()
  193. self._update_list(other_tmp_properties, 'cc_flags')
  194. self._update_list(other_tmp_properties, 'cc_flags_after')
  195. self._update_list(other_tmp_properties, 'extra_objs')
  196. self._update_dict(other_tmp_properties, 'test_run_args')
  197. return self.properties.update(other_tmp_properties)
  198. class PrefixTree:
  199. def __init__(self, path_properties_dict=None, children=None):
  200. if path_properties_dict is None:
  201. path_properties_dict = {}
  202. if children is None:
  203. children = {}
  204. self.children = children
  205. self.path_properties = PathProperties(path_properties_dict)
  206. @staticmethod
  207. def make_from_tuples(tuples):
  208. def tree_from_tuples(tuple_):
  209. if not type(tuple_) is tuple:
  210. tuple_ = (tuple_, {})
  211. cur_properties, cur_children = tuple_
  212. return PrefixTree(cur_properties, cur_children)
  213. top_tree = tree_from_tuples(tuples)
  214. todo_trees = [top_tree]
  215. while todo_trees:
  216. cur_tree = todo_trees.pop()
  217. cur_children = cur_tree.children
  218. for child_key in cur_children:
  219. new_tree = tree_from_tuples(cur_children[child_key])
  220. cur_children[child_key] = new_tree
  221. todo_trees.append(new_tree)
  222. return top_tree
  223. def get(path):
  224. cur_node = path_properties_tree
  225. path_components = path.split(os.sep)
  226. path_properties = PathProperties(cur_node.path_properties.properties.copy())
  227. for path_component in path_components:
  228. if path_component in cur_node.children:
  229. cur_node = cur_node.children[path_component]
  230. path_properties.update(cur_node.path_properties)
  231. else:
  232. break
  233. path_properties.set_path_components(path_components)
  234. return path_properties
  235. gnu_extension_properties = {
  236. 'c_std': 'gnu11',
  237. 'cc_pedantic': False,
  238. 'cxx_std': 'gnu++17'
  239. }
  240. freestanding_properties = {
  241. 'baremetal': False,
  242. 'cc_flags': [
  243. '-ffreestanding', LF,
  244. '-nostdlib', LF,
  245. '-static', LF,
  246. ],
  247. 'extra_objs_lkmc_common': False,
  248. }
  249. # See: https://github.com/cirosantilli/linux-kernel-module-cheat#path-properties
  250. path_properties_tuples = (
  251. PathProperties.default_properties,
  252. {
  253. 'baremetal': (
  254. {
  255. 'baremetal': True,
  256. },
  257. {
  258. 'arch': (
  259. {},
  260. {
  261. 'arm': (
  262. {'allowed_archs': {'arm'}},
  263. {
  264. 'multicore.S': {'test_run_args': {'cpus': 2}},
  265. 'no_bootloader': (
  266. {'extra_objs_disable_baremetal_bootloader': True},
  267. {
  268. 'gem5_exit.S': {'requires_m5ops': True},
  269. 'semihost_exit.S': {'requires_semihosting': True},
  270. }
  271. ),
  272. 'return1.S': {'exit_status': 1},
  273. 'semihost_exit.S': {'requires_semihosting': True},
  274. },
  275. ),
  276. 'aarch64': (
  277. {'allowed_archs': {'aarch64'}},
  278. {
  279. 'multicore.S': {'test_run_args': {'cpus': 2}},
  280. 'no_bootloader': (
  281. {'extra_objs_disable_baremetal_bootloader': True},
  282. {
  283. 'gem5_exit.S': {'requires_m5ops': True},
  284. 'semihost_exit.S': {'requires_semihosting': True},
  285. }
  286. ),
  287. 'return1.S': {'exit_status': 1},
  288. 'semihost_exit.S': {'requires_semihosting': True},
  289. },
  290. )
  291. }
  292. ),
  293. 'lib': {'no_executable': True},
  294. 'getchar.c': {'interactive': True},
  295. }
  296. ),
  297. 'lkmc.c': {
  298. 'baremetal': True,
  299. 'userland': True,
  300. },
  301. 'userland': (
  302. {
  303. 'userland': True,
  304. },
  305. {
  306. 'arch': (
  307. {
  308. 'baremetal': True,
  309. 'extra_objs_lkmc_common': True,
  310. },
  311. {
  312. 'arm': (
  313. {
  314. 'allowed_archs': {'arm'},
  315. 'cc_flags': [
  316. '-Xassembler', '-mcpu=cortex-a72', LF,
  317. # To prevent:
  318. # > vfp.S: Error: selected processor does not support <FPU instruction> in ARM mode
  319. # https://stackoverflow.com/questions/41131432/cross-compiling-error-selected-processor-does-not-support-fmrx-r3-fpexc-in/52875732#52875732
  320. # We aim to take the most extended mode currently available that works on QEMU.
  321. '-Xassembler', '-mfpu=crypto-neon-fp-armv8.1', LF,
  322. '-Xassembler', '-meabi=5', LF,
  323. # Treat inline assembly as arm instead of thumb
  324. # The opposite of -mthumb.
  325. '-marm', LF,
  326. # Make gcc generate .syntax unified for inline assembly.
  327. # However, it gets ignored if -marm is given, which a GCC bug that was recently fixed:
  328. # https://stackoverflow.com/questions/54078112/how-to-write-syntax-unified-ual-armv7-inline-assembly-in-gcc/54132097#54132097
  329. # So we just write divided inline assembly for now.
  330. '-masm-syntax-unified', LF,
  331. ]
  332. },
  333. {
  334. 'inline_asm': (
  335. {
  336. },
  337. {
  338. 'freestanding': freestanding_properties,
  339. },
  340. ),
  341. 'freestanding': freestanding_properties,
  342. 'lkmc_assert_eq_fail.S': {'signal_received': signal.Signals.SIGABRT},
  343. 'lkmc_assert_memcmp_fail.S': {'signal_received': signal.Signals.SIGABRT},
  344. 'udf.S': {
  345. 'signal_generated_by_os': True,
  346. 'signal_received': signal.Signals.SIGILL,
  347. },
  348. 'vcvta.S': {
  349. 'arm_aarch32': True,
  350. 'gem5_unimplemented_instruction': True,
  351. },
  352. }
  353. ),
  354. 'aarch64': (
  355. {'allowed_archs': {'aarch64'}},
  356. {
  357. 'inline_asm': (
  358. {
  359. },
  360. {
  361. 'freestanding': freestanding_properties,
  362. },
  363. ),
  364. 'freestanding': freestanding_properties,
  365. 'lkmc_assert_eq_fail.S': {'signal_received': signal.Signals.SIGABRT},
  366. 'lkmc_assert_memcmp_fail.S': {'signal_received': signal.Signals.SIGABRT},
  367. 'udf.S': {
  368. 'signal_generated_by_os': True,
  369. 'signal_received': signal.Signals.SIGILL,
  370. },
  371. }
  372. ),
  373. 'lkmc_assert_fail.S': {
  374. 'signal_received': signal.Signals.SIGABRT,
  375. },
  376. 'x86_64': (
  377. {'allowed_archs': {'x86_64'}},
  378. {
  379. 'inline_asm': (
  380. {
  381. },
  382. {
  383. 'freestanding': freestanding_properties,
  384. }
  385. ),
  386. 'freestanding': freestanding_properties,
  387. 'lkmc_assert_eq_fail.S': {'signal_received': signal.Signals.SIGABRT},
  388. 'lkmc_assert_memcmp_fail.S': {'signal_received': signal.Signals.SIGABRT},
  389. 'ring0.c': {
  390. 'signal_received': signal.Signals.SIGSEGV,
  391. }
  392. }
  393. ),
  394. }
  395. ),
  396. 'c': (
  397. {
  398. 'baremetal': True,
  399. },
  400. {
  401. 'abort.c': {
  402. 'signal_received': signal.Signals.SIGABRT,
  403. },
  404. 'assert_fail.c': {
  405. 'signal_received': signal.Signals.SIGABRT,
  406. },
  407. 'exit1.c': {'exit_status': 1},
  408. 'exit2.c': {'exit_status': 2},
  409. 'false.c': {'exit_status': 1},
  410. 'getchar.c': {'interactive': True},
  411. 'infinite_loop.c': {'more_than_1s': True},
  412. 'out_of_memory.c': {'disrupts_system': True},
  413. 'return1.c': {'exit_status': 1},
  414. 'return2.c': {'exit_status': 2},
  415. }
  416. ),
  417. 'gcc': (
  418. gnu_extension_properties,
  419. {
  420. 'openmp.c': {'cc_flags': ['-fopenmp', LF]},
  421. }
  422. ),
  423. 'gdb_tests': {'baremetal': True},
  424. 'kernel_modules': {**gnu_extension_properties, **{'requires_kernel_modules': True}},
  425. 'libs': (
  426. {'requires_dynamic_library': True},
  427. {
  428. 'libdrm': {'requires_sudo': True},
  429. }
  430. ),
  431. 'linux': (
  432. gnu_extension_properties,
  433. {
  434. 'ctrl_alt_del.c': {'requires_sudo': True},
  435. 'init_env_poweroff.c': {'requires_sudo': True},
  436. 'myinsmod.c': {'requires_sudo': True},
  437. 'myrmmod.c': {'requires_sudo': True},
  438. 'pagemap_dump.c': {'requires_argument': True},
  439. 'poweroff.c': {'requires_sudo': True},
  440. 'proc_events.c': {'requires_sudo': True},
  441. 'proc_events.c': {'requires_sudo': True},
  442. 'sched_getaffinity.c': {'requires_syscall_getcpu': True},
  443. 'sched_getaffinity_threads.c': {
  444. 'cc_flags_after': ['-pthread', LF],
  445. 'more_than_1s': True,
  446. 'requires_syscall_getcpu': True,
  447. },
  448. 'time_boot.c': {'requires_sudo': True},
  449. 'virt_to_phys_user.c': {'requires_argument': True},
  450. }
  451. ),
  452. 'posix': (
  453. {},
  454. {
  455. 'count.c': {'more_than_1s': True},
  456. 'kill.c': {
  457. 'baremetal': True,
  458. 'signal_received': signal.Signals.SIGHUP,
  459. },
  460. 'sleep_forever.c': {'more_than_1s': True},
  461. 'virt_to_phys_test.c': {'more_than_1s': True},
  462. }
  463. ),
  464. }
  465. ),
  466. }
  467. )
  468. path_properties_tree = PrefixTree.make_from_tuples(path_properties_tuples)