path_properties.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  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. 'interactive': False,
  48. # The script takes a perceptible amount of time to run. Possibly an infinite loop.
  49. 'more_than_1s': False,
  50. # The path should not be built. E.g., it is symlinked into multiple archs.
  51. 'no_build': False,
  52. # The path does not generate an executable in itself, e.g.
  53. # it only generates intermediate object files. Therefore it
  54. # should not be run while testing.
  55. 'no_executable': False,
  56. # The script requires a non-trivial argument to be passed to run properly.
  57. 'requires_argument': False,
  58. 'requires_dynamic_library': False,
  59. 'requires_m5ops': False,
  60. # gem5 fatal: syscall getcpu (#168) unimplemented.
  61. 'requires_syscall_getcpu': False,
  62. 'requires_semihosting': False,
  63. # Requires certain of our custom kernel modules to be inserted to run.
  64. 'requires_kernel_modules': False,
  65. # The example requires sudo, which usually implies that it can do something
  66. # deeply to the system it runs on, which would preventing further interactive
  67. # or test usage of the system, for example poweroff or messing up the GUI.
  68. 'requires_sudo': False,
  69. # The signal received is generated by the OS implicitly rather than explicitly
  70. # done sith signal(), e.g. Illegal instruction or sigsegv.
  71. # Therefore, it won't behave in the same way in baremetal. aarch64 already
  72. # has an exception handler which we could use but arm doesn't and the IP
  73. # goes astray, so let's just skip this for now.
  74. 'signal_generated_by_os': False,
  75. 'signal_received': None,
  76. # We were lazy to properly classify why we are skipping these tests.
  77. # TODO get it done.
  78. 'skip_run_unclassified': False,
  79. # Aruments added automatically to run when running tests,
  80. # but not on manual running.
  81. 'test_run_args': {},
  82. # Examples that can be built in userland.
  83. 'userland': False,
  84. }
  85. '''
  86. Encodes properties of userland and baremetal paths.
  87. For directories, it applies to all files under the directory.
  88. Used to determine how to build and test the examples.
  89. '''
  90. def __init__(
  91. self,
  92. properties
  93. ):
  94. for key in properties:
  95. if not key in self.default_properties:
  96. raise ValueError('Unknown key: {}'.format(key))
  97. self.properties = properties.copy()
  98. def __getitem__(self, key):
  99. return self.properties[key]
  100. def __repr__(self):
  101. return str(self.properties)
  102. def set_path_components(self, path_components):
  103. self.path_components = path_components
  104. def should_be_built(
  105. self,
  106. env,
  107. link=False,
  108. is_baremetal=False,
  109. is_userland=False,
  110. ):
  111. if len(self.path_components) > 1 and \
  112. self.path_components[1] == 'libs' and \
  113. not env['package_all'] and \
  114. not self.path_components[2] in env['package']:
  115. return False
  116. return \
  117. not self['no_build'] and \
  118. (
  119. self['allowed_archs'] is None or
  120. env['arch'] in self['allowed_archs']
  121. ) and \
  122. (
  123. (is_userland and self['userland'] ) or
  124. (is_baremetal and self['baremetal'])
  125. ) and \
  126. not (
  127. link and
  128. self['no_executable']
  129. )
  130. def should_be_tested(self, env, is_baremetal=False, is_userland=False):
  131. return (
  132. self.should_be_built(
  133. env,
  134. is_baremetal=is_baremetal,
  135. is_userland=is_userland
  136. ) and
  137. not (
  138. is_baremetal and (
  139. self['arm_aarch32'] or
  140. self['signal_generated_by_os']
  141. )
  142. ) and
  143. not self['interactive'] and
  144. not self['more_than_1s'] and
  145. not self['no_executable'] and
  146. not self['requires_argument'] and
  147. not self['requires_kernel_modules'] and
  148. not self['requires_sudo'] and
  149. not self['skip_run_unclassified'] and
  150. not (
  151. env['emulator'] == 'gem5' and
  152. (
  153. self['requires_dynamic_library'] or
  154. self['requires_semihosting'] or
  155. self['requires_syscall_getcpu']
  156. )
  157. ) and
  158. not (
  159. env['emulator'] == 'qemu' and
  160. (
  161. self['requires_m5ops']
  162. )
  163. )
  164. )
  165. def _update_dict(self, other_tmp_properties, key):
  166. if key in self.properties and key in other_tmp_properties:
  167. other_tmp_properties[key] = {
  168. **self.properties[key],
  169. **other_tmp_properties[key]
  170. }
  171. def _update_list(self, other_tmp_properties, key):
  172. if key in self.properties and key in other_tmp_properties:
  173. other_tmp_properties[key] = \
  174. self.properties[key] + \
  175. other_tmp_properties[key]
  176. def update(self, other):
  177. other_tmp_properties = other.properties.copy()
  178. self._update_list(other_tmp_properties, 'cc_flags')
  179. self._update_list(other_tmp_properties, 'cc_flags_after')
  180. self._update_list(other_tmp_properties, 'extra_objs')
  181. self._update_dict(other_tmp_properties, 'test_run_args')
  182. return self.properties.update(other_tmp_properties)
  183. class PrefixTree:
  184. def __init__(self, path_properties_dict=None, children=None):
  185. if path_properties_dict is None:
  186. path_properties_dict = {}
  187. if children is None:
  188. children = {}
  189. self.children = children
  190. self.path_properties = PathProperties(path_properties_dict)
  191. @staticmethod
  192. def make_from_tuples(tuples):
  193. def tree_from_tuples(tuple_):
  194. if not type(tuple_) is tuple:
  195. tuple_ = (tuple_, {})
  196. cur_properties, cur_children = tuple_
  197. return PrefixTree(cur_properties, cur_children)
  198. top_tree = tree_from_tuples(tuples)
  199. todo_trees = [top_tree]
  200. while todo_trees:
  201. cur_tree = todo_trees.pop()
  202. cur_children = cur_tree.children
  203. for child_key in cur_children:
  204. new_tree = tree_from_tuples(cur_children[child_key])
  205. cur_children[child_key] = new_tree
  206. todo_trees.append(new_tree)
  207. return top_tree
  208. def get(path):
  209. cur_node = path_properties_tree
  210. path_components = path.split(os.sep)
  211. path_properties = PathProperties(cur_node.path_properties.properties.copy())
  212. for path_component in path_components:
  213. if path_component in cur_node.children:
  214. cur_node = cur_node.children[path_component]
  215. path_properties.update(cur_node.path_properties)
  216. else:
  217. break
  218. path_properties.set_path_components(path_components)
  219. return path_properties
  220. gnu_extension_properties = {
  221. 'c_std': 'gnu11',
  222. 'cc_pedantic': False,
  223. 'cxx_std': 'gnu++17'
  224. }
  225. freestanding_properties = {
  226. 'baremetal': False,
  227. 'cc_flags': [
  228. '-ffreestanding', LF,
  229. '-nostdlib', LF,
  230. '-static', LF,
  231. ],
  232. 'extra_objs_lkmc_common': False,
  233. }
  234. # See: https://github.com/cirosantilli/linux-kernel-module-cheat#path-properties
  235. path_properties_tuples = (
  236. PathProperties.default_properties,
  237. {
  238. 'baremetal': (
  239. {
  240. 'baremetal': True,
  241. },
  242. {
  243. 'arch': (
  244. {},
  245. {
  246. 'arm': (
  247. {'allowed_archs': {'arm'}},
  248. {
  249. 'multicore.S': {'test_run_args': {'cpus': 2}},
  250. 'no_bootloader': (
  251. {'extra_objs_disable_baremetal_bootloader': True},
  252. {
  253. 'gem5_exit.S': {'requires_m5ops': True},
  254. 'semihost_exit.S': {'requires_semihosting': True},
  255. }
  256. ),
  257. 'return1.S': {'exit_status': 1},
  258. 'semihost_exit.S': {'requires_semihosting': True},
  259. },
  260. ),
  261. 'aarch64': (
  262. {'allowed_archs': {'aarch64'}},
  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. }
  277. ),
  278. 'lib': {'no_executable': True},
  279. 'getchar.c': {'interactive': True},
  280. }
  281. ),
  282. 'lkmc.c': {
  283. 'baremetal': True,
  284. 'userland': True,
  285. },
  286. 'userland': (
  287. {
  288. 'userland': True,
  289. },
  290. {
  291. 'arch': (
  292. {
  293. 'baremetal': True,
  294. 'extra_objs_lkmc_common': True,
  295. },
  296. {
  297. 'arm': (
  298. {
  299. 'allowed_archs': {'arm'},
  300. 'cc_flags': [
  301. '-Xassembler', '-mcpu=cortex-a72', LF,
  302. # To prevent:
  303. # > vfp.S: Error: selected processor does not support <FPU instruction> in ARM mode
  304. # https://stackoverflow.com/questions/41131432/cross-compiling-error-selected-processor-does-not-support-fmrx-r3-fpexc-in/52875732#52875732
  305. # We aim to take the most extended mode currently available that works on QEMU.
  306. '-Xassembler', '-mfpu=crypto-neon-fp-armv8.1', LF,
  307. '-Xassembler', '-meabi=5', LF,
  308. # Treat inline assembly as arm instead of thumb
  309. # The opposite of -mthumb.
  310. '-marm', LF,
  311. # Make gcc generate .syntax unified for inline assembly.
  312. # However, it gets ignored if -marm is given, which a GCC bug that was recently fixed:
  313. # https://stackoverflow.com/questions/54078112/how-to-write-syntax-unified-ual-armv7-inline-assembly-in-gcc/54132097#54132097
  314. # So we just write divided inline assembly for now.
  315. '-masm-syntax-unified', LF,
  316. ]
  317. },
  318. {
  319. 'c': (
  320. {
  321. },
  322. {
  323. 'freestanding': freestanding_properties,
  324. },
  325. ),
  326. 'freestanding': freestanding_properties,
  327. 'lkmc_assert_eq_fail.S': {'signal_received': signal.Signals.SIGABRT},
  328. 'lkmc_assert_memcmp_fail.S': {'signal_received': signal.Signals.SIGABRT},
  329. 'udf.S': {
  330. 'signal_generated_by_os': True,
  331. 'signal_received': signal.Signals.SIGILL,
  332. },
  333. 'vcvta.S': {'arm_aarch32': True},
  334. }
  335. ),
  336. 'aarch64': (
  337. {'allowed_archs': {'aarch64'}},
  338. {
  339. 'c': (
  340. {
  341. },
  342. {
  343. 'freestanding': freestanding_properties,
  344. },
  345. ),
  346. 'freestanding': freestanding_properties,
  347. 'lkmc_assert_eq_fail.S': {'signal_received': signal.Signals.SIGABRT},
  348. 'lkmc_assert_memcmp_fail.S': {'signal_received': signal.Signals.SIGABRT},
  349. 'udf.S': {
  350. 'signal_generated_by_os': True,
  351. 'signal_received': signal.Signals.SIGILL,
  352. },
  353. }
  354. ),
  355. 'lkmc_assert_fail.S': {
  356. 'signal_received': signal.Signals.SIGABRT,
  357. },
  358. 'x86_64': (
  359. {'allowed_archs': {'x86_64'}},
  360. {
  361. 'c': (
  362. {
  363. },
  364. {
  365. 'freestanding': freestanding_properties,
  366. 'ring0.c': {
  367. 'signal_received': signal.Signals.SIGSEGV
  368. }
  369. }
  370. ),
  371. 'freestanding': freestanding_properties,
  372. 'lkmc_assert_eq_fail.S': {'signal_received': signal.Signals.SIGABRT},
  373. 'lkmc_assert_memcmp_fail.S': {'signal_received': signal.Signals.SIGABRT},
  374. }
  375. ),
  376. }
  377. ),
  378. 'c': (
  379. {
  380. 'baremetal': True,
  381. },
  382. {
  383. 'abort.c': {
  384. 'signal_received': signal.Signals.SIGABRT,
  385. },
  386. 'assert_fail.c': {
  387. 'signal_received': signal.Signals.SIGABRT,
  388. },
  389. 'exit1.c': {'exit_status': 1},
  390. 'exit2.c': {'exit_status': 2},
  391. 'false.c': {'exit_status': 1},
  392. 'getchar.c': {'interactive': True},
  393. 'infinite_loop.c': {'more_than_1s': True},
  394. 'out_of_memory.c': {'disrupts_system': True},
  395. 'return1.c': {'exit_status': 1},
  396. 'return2.c': {'exit_status': 2},
  397. }
  398. ),
  399. 'gcc': (
  400. gnu_extension_properties,
  401. {
  402. 'openmp.c': {'cc_flags': ['-fopenmp', LF]},
  403. }
  404. ),
  405. 'kernel_modules': {**gnu_extension_properties, **{'requires_kernel_modules': True}},
  406. 'libs': (
  407. {'requires_dynamic_library': True},
  408. {
  409. 'libdrm': {'requires_sudo': True},
  410. }
  411. ),
  412. 'linux': (
  413. gnu_extension_properties,
  414. {
  415. 'ctrl_alt_del.c': {'requires_sudo': True},
  416. 'init_env_poweroff.c': {'requires_sudo': True},
  417. 'myinsmod.c': {'requires_sudo': True},
  418. 'myrmmod.c': {'requires_sudo': True},
  419. 'pagemap_dump.c': {'requires_argument': True},
  420. 'poweroff.c': {'requires_sudo': True},
  421. 'proc_events.c': {'requires_sudo': True},
  422. 'proc_events.c': {'requires_sudo': True},
  423. 'sched_getaffinity.c': {'requires_syscall_getcpu': True},
  424. 'sched_getaffinity_threads.c': {
  425. 'cc_flags_after': ['-pthread', LF],
  426. 'more_than_1s': True,
  427. 'requires_syscall_getcpu': True,
  428. },
  429. 'time_boot.c': {'requires_sudo': True},
  430. 'virt_to_phys_user.c': {'requires_argument': True},
  431. }
  432. ),
  433. 'posix': (
  434. {},
  435. {
  436. 'count.c': {'more_than_1s': True},
  437. 'sleep_forever.c': {'more_than_1s': True},
  438. 'virt_to_phys_test.c': {'more_than_1s': True},
  439. }
  440. ),
  441. }
  442. ),
  443. }
  444. )
  445. path_properties_tree = PrefixTree.make_from_tuples(path_properties_tuples)