OLD | NEW |
| (Empty) |
1 #!/usr/bin/python | |
2 # Copyright 2014 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 # pylint: disable=W0104,W0106,F0401,R0201 | |
7 | |
8 import errno | |
9 import os.path | |
10 import sys | |
11 | |
12 import interface | |
13 | |
14 | |
15 def ScriptDir(): | |
16 return os.path.dirname(os.path.abspath(__file__)) | |
17 | |
18 | |
19 def RepoRoot(): | |
20 # Go up 4 levels, to get out of "mojo/nacl/sfi/nacl_bindings_generator". | |
21 return os.path.dirname(os.path.dirname(os.path.dirname( | |
22 os.path.dirname(ScriptDir())))) | |
23 | |
24 | |
25 def _AddThirdPartyImportPath(): | |
26 sys.path.append(os.path.join(RepoRoot(), 'third_party')) | |
27 | |
28 | |
29 _AddThirdPartyImportPath() | |
30 import jinja2 | |
31 | |
32 loader = jinja2.FileSystemLoader(ScriptDir()) | |
33 jinja_env = jinja2.Environment(loader=loader, keep_trailing_newline=True) | |
34 | |
35 | |
36 # Accumulate lines of code with varying levels of indentation. | |
37 class CodeWriter(object): | |
38 def __init__(self): | |
39 self._lines = [] | |
40 self._margin = '' | |
41 self._margin_stack = [] | |
42 | |
43 def __lshift__(self, line): | |
44 self._lines.append((self._margin + line).rstrip()) | |
45 | |
46 def PushMargin(self): | |
47 self._margin_stack.append(self._margin) | |
48 self._margin += ' ' | |
49 | |
50 def PopMargin(self): | |
51 self._margin = self._margin_stack.pop() | |
52 | |
53 def GetValue(self): | |
54 return '\n'.join(self._lines).rstrip() + '\n' | |
55 | |
56 def Indent(self): | |
57 return Indent(self) | |
58 | |
59 | |
60 # Context handler that automatically indents and dedents a CodeWriter | |
61 class Indent(object): | |
62 def __init__(self, writer): | |
63 self._writer = writer | |
64 | |
65 def __enter__(self): | |
66 self._writer.PushMargin() | |
67 | |
68 def __exit__(self, type_, value, traceback): | |
69 self._writer.PopMargin() | |
70 | |
71 | |
72 def TemplateFile(name): | |
73 return os.path.join(os.path.dirname(__file__), name) | |
74 | |
75 | |
76 # Wraps comma separated lists as needed. | |
77 # TODO(teravest): Eliminate Wrap() and use "git cl format" when code is checked | |
78 # in. | |
79 def Wrap(pre, items, post): | |
80 complete = pre + ', '.join(items) + post | |
81 if len(complete) <= 80: | |
82 return [complete] | |
83 lines = [pre] | |
84 indent = ' ' | |
85 for i, item in enumerate(items): | |
86 if i < len(items) - 1: | |
87 lines.append(indent + item + ',') | |
88 else: | |
89 lines.append(indent + item + post) | |
90 return lines | |
91 | |
92 | |
93 def GeneratorWarning(): | |
94 return ('// WARNING this file was generated by %s\n// Do not edit by hand.' % | |
95 os.path.basename(__file__)) | |
96 | |
97 | |
98 # Untrusted library which thunks from the public Mojo API to the IRT interface | |
99 # implementing the public Mojo API. | |
100 def GenerateLibMojo(functions, common_vars, out): | |
101 template = jinja_env.get_template('libmojo.cc.tmpl') | |
102 | |
103 code = CodeWriter() | |
104 | |
105 for f in functions: | |
106 for line in Wrap('%s %s(' % (f.return_type, f.name), f.ParamList(), ') {'): | |
107 code << line | |
108 | |
109 with code.Indent(): | |
110 code << 'struct nacl_irt_mojo* irt_mojo = get_irt_mojo();' | |
111 code << 'if (!irt_mojo)' | |
112 with code.Indent(): | |
113 code << 'abort();' | |
114 code << 'return irt_mojo->%s(%s);' % ( | |
115 f.name, ', '.join([p.name for p in f.params])) | |
116 | |
117 code << '}' | |
118 code << '' | |
119 | |
120 body = code.GetValue() | |
121 text = template.render( | |
122 body=body, | |
123 **common_vars) | |
124 out.write(text) | |
125 | |
126 | |
127 # Parameters passed into trusted code are handled differently depending on | |
128 # details of the parameter. ParamImpl instances encapsulate these differences | |
129 # and are used to generate the code that transfers parameters across the | |
130 # untrusted/trusted boundary. | |
131 class ParamImpl(object): | |
132 def __init__(self, param): | |
133 self.param = param | |
134 | |
135 # Declare whatever variables are needed to handle this particular parameter. | |
136 def DeclareVars(self, code): | |
137 raise NotImplementedError(type(self)) | |
138 | |
139 # Convert the untrusted representation of the parameter into a trusted | |
140 # representation, such as a scalar value or a trusted pointer into the | |
141 # untrusted address space. | |
142 def ConvertParam(self): | |
143 raise NotImplementedError(type(self)) | |
144 | |
145 # For this particular parameter, what expression should be passed when | |
146 # invoking the trusted Mojo API function? | |
147 def CallParam(self): | |
148 raise NotImplementedError(type(self)) | |
149 | |
150 # After invoking the trusted Mojo API function, transfer data back into | |
151 # untrusted memory. Overriden for Out and InOut parameters. | |
152 def CopyOut(self, code): | |
153 pass | |
154 | |
155 # Converting array parameters needs to be defered until after the scalar | |
156 # parameter containing the size of the array has itself been converted. | |
157 def IsArray(self): | |
158 return False | |
159 | |
160 | |
161 class ScalarInputImpl(ParamImpl): | |
162 def DeclareVars(self, code): | |
163 code << '%s %s_value;' % (self.param.base_type, self.param.name) | |
164 | |
165 def ConvertParam(self): | |
166 p = self.param | |
167 return ('ConvertScalarInput(nap, params[%d], &%s_value)' % | |
168 (p.uid + 1, p.name)) | |
169 | |
170 def CallParam(self): | |
171 return '%s_value' % self.param.name | |
172 | |
173 | |
174 class ScalarOutputImpl(ParamImpl): | |
175 def DeclareVars(self, code): | |
176 code << '%s volatile* %s_ptr;' % (self.param.base_type, self.param.name) | |
177 code << '%s %s_value;' % (self.param.base_type, self.param.name) | |
178 | |
179 def ConvertParam(self): | |
180 p = self.param | |
181 return ('ConvertScalarOutput(nap, params[%d], %s, &%s_ptr)' % | |
182 (p.uid + 1, CBool(p.is_optional), p.name)) | |
183 | |
184 def CallParam(self): | |
185 name = self.param.name | |
186 expr = '&%s_value' % name | |
187 if self.param.is_optional: | |
188 expr = '%s_ptr ? %s : nullptr' % (name, expr) | |
189 return expr | |
190 | |
191 def CopyOut(self, code): | |
192 name = self.param.name | |
193 if self.param.is_struct: | |
194 # C++ errors when you try to copy a volatile struct pointer. | |
195 # (There are no default copy constructors for this case.) | |
196 # memcpy instead. | |
197 copy_stmt = ('memcpy_volatile_out(%s_ptr, &%s_value, sizeof(%s));' % | |
198 (name, name, self.param.base_type)) | |
199 else: | |
200 copy_stmt = '*%s_ptr = %s_value;' % (name, name) | |
201 | |
202 if self.param.is_optional: | |
203 code << 'if (%s_ptr) {' % (name) | |
204 with code.Indent(): | |
205 code << copy_stmt | |
206 code << '}' | |
207 else: | |
208 code << copy_stmt | |
209 | |
210 | |
211 class ScalarInOutImpl(ParamImpl): | |
212 def DeclareVars(self, code): | |
213 code << '%s volatile* %s_ptr;' % (self.param.base_type, self.param.name) | |
214 code << '%s %s_value;' % (self.param.base_type, self.param.name) | |
215 | |
216 def ConvertParam(self): | |
217 p = self.param | |
218 return ('ConvertScalarInOut(nap, params[%d], %s, &%s_value, &%s_ptr)' % | |
219 (p.uid + 1, CBool(p.is_optional), p.name, p.name)) | |
220 | |
221 def CallParam(self): | |
222 name = self.param.name | |
223 expr = '&%s_value' % name | |
224 if self.param.is_optional: | |
225 expr = '%s_ptr ? %s : nullptr' % (name, expr) | |
226 return expr | |
227 | |
228 def CopyOut(self, code): | |
229 name = self.param.name | |
230 if self.param.is_optional: | |
231 code << 'if (%s_ptr) {' % (name) | |
232 with code.Indent(): | |
233 code << '*%s_ptr = %s_value;' % (name, name) | |
234 code << '}' | |
235 else: | |
236 code << '*%s_ptr = %s_value;' % (name, name) | |
237 | |
238 | |
239 class PointerInputImpl(ParamImpl): | |
240 def DeclareVars(self, code): | |
241 code << '%s %s_value;' % (self.param.base_type, self.param.name) | |
242 | |
243 def ConvertParam(self): | |
244 p = self.param | |
245 return ('ConvertPointerInput(nap, params[%d], &%s_value)' % | |
246 (p.uid + 1, p.name)) | |
247 | |
248 def CallParam(self): | |
249 return '%s_value' % self.param.name | |
250 | |
251 | |
252 class PointerInOutImpl(ParamImpl): | |
253 def DeclareVars(self, code): | |
254 code << 'uint32_t volatile* %s_ptr;' % (self.param.name,) | |
255 code << '%s %s_value;' % (self.param.base_type, self.param.name) | |
256 | |
257 def ConvertParam(self): | |
258 p = self.param | |
259 return ('ConvertPointerInOut(nap, params[%d], %s, &%s_value, &%s_ptr)' % | |
260 (p.uid + 1, CBool(p.is_optional), p.name, p.name)) | |
261 | |
262 def CallParam(self): | |
263 name = self.param.name | |
264 expr = '&%s_value' % name | |
265 if self.param.is_optional: | |
266 expr = '%s_ptr ? %s : nullptr' % (name, expr) | |
267 return expr | |
268 | |
269 def CopyOut(self, code): | |
270 assert not self.param.is_optional | |
271 name = self.param.name | |
272 if self.param.is_optional: | |
273 code << 'if (%s_ptr) {' % (name) | |
274 with code.Indent(): | |
275 code << 'CopyOutPointer(nap, %s_value, %s_ptr);' % (name, name) | |
276 code << '}' | |
277 else: | |
278 code << 'CopyOutPointer(nap, %s_value, %s_ptr);' % (name, name) | |
279 | |
280 | |
281 class ArrayImpl(ParamImpl): | |
282 def DeclareVars(self, code): | |
283 code << '%s %s;' % (self.param.param_type, self.param.name) | |
284 | |
285 def ConvertParam(self): | |
286 p = self.param | |
287 if p.base_type == 'void': | |
288 element_size = '1' | |
289 else: | |
290 element_size = 'sizeof(*%s)' % p.name | |
291 | |
292 return ('ConvertArray(nap, params[%d], %s, %s, %s, &%s)' % | |
293 (p.uid + 1, p.size + '_value', element_size, CBool(p.is_optional), | |
294 p.name)) | |
295 | |
296 def CallParam(self): | |
297 return self.param.name | |
298 | |
299 def IsArray(self): | |
300 return True | |
301 | |
302 | |
303 class ExtensibleStructInputImpl(ParamImpl): | |
304 def DeclareVars(self, code): | |
305 code << '%s %s;' % (self.param.param_type, self.param.name) | |
306 | |
307 def ConvertParam(self): | |
308 p = self.param | |
309 return ('ConvertExtensibleStructInput(nap, params[%d], %s, &%s)' % | |
310 (p.uid + 1, CBool(p.is_optional), p.name)) | |
311 | |
312 def CallParam(self): | |
313 return self.param.name | |
314 | |
315 | |
316 # We can handle extensible out structs just like we handle output arrays, except | |
317 # that the (input buffer) size is always in bytes. | |
318 class ExtensibleStructOutputImpl(ParamImpl): | |
319 def DeclareVars(self, code): | |
320 code << '%s %s;' % (self.param.param_type, self.param.name) | |
321 | |
322 def ConvertParam(self): | |
323 p = self.param | |
324 return ('ConvertArray(nap, params[%d], %s, %s, %s, &%s)' % | |
325 (p.uid + 1, p.size + '_value', '1', CBool(p.is_optional), p.name)) | |
326 | |
327 def CallParam(self): | |
328 return self.param.name | |
329 | |
330 def IsArray(self): | |
331 return True | |
332 | |
333 | |
334 def ImplForParam(p): | |
335 if p.IsScalar(): | |
336 if p.is_output: | |
337 if p.is_pointer: | |
338 return PointerInOutImpl(p) | |
339 elif p.is_input: | |
340 return ScalarInOutImpl(p) | |
341 else: | |
342 if p.is_always_written: | |
343 return ScalarOutputImpl(p) | |
344 else: | |
345 # Mojo defines that some of its outputs will not be set in specific | |
346 # cases. To avoid the complexity of determining if the output was set | |
347 # by Mojo, copy the output's current value (possibly junk) and copy it | |
348 # back to untrusted memory afterwards. | |
349 return ScalarInOutImpl(p) | |
350 else: | |
351 if p.is_pointer: | |
352 return PointerInputImpl(p) | |
353 else: | |
354 return ScalarInputImpl(p) | |
355 elif p.is_array: | |
356 return ArrayImpl(p) | |
357 elif p.is_struct: | |
358 if p.is_input and not p.is_output and p.is_extensible: | |
359 return ExtensibleStructInputImpl(p) | |
360 if p.is_output and not p.is_input and p.is_extensible: | |
361 return ExtensibleStructOutputImpl(p) | |
362 if not p.is_input and p.is_output and not p.is_extensible: | |
363 return ScalarOutputImpl(p) | |
364 assert False, p.name | |
365 | |
366 | |
367 def CBool(value): | |
368 return 'true' if value else 'false' | |
369 | |
370 | |
371 # A trusted wrapper that validates the arguments passed from untrusted code | |
372 # before passing them to the underlying public Mojo API. | |
373 def GenerateMojoSyscall(functions, common_vars, out): | |
374 template = jinja_env.get_template('mojo_syscall.cc.tmpl') | |
375 | |
376 code = CodeWriter() | |
377 code.PushMargin() | |
378 | |
379 for f in functions: | |
380 is_implemented = not f.broken_in_nacl | |
381 | |
382 impls = [ImplForParam(p) for p in f.params] | |
383 impls.append(ImplForParam(f.result_param)) | |
384 | |
385 code << 'case %d:' % f.uid | |
386 | |
387 if not is_implemented: | |
388 with code.Indent(): | |
389 code << 'fprintf(stderr, "%s not implemented\\n");' % f.name | |
390 code << 'return -1;' | |
391 continue | |
392 | |
393 code.PushMargin() | |
394 | |
395 code << '{' | |
396 | |
397 with code.Indent(): | |
398 num_params = len(f.params) + 2 | |
399 code << 'if (num_params != %d) {' % num_params | |
400 with code.Indent(): | |
401 code << 'return -1;' | |
402 code << '}' | |
403 | |
404 # Declare temporaries. | |
405 for impl in impls: | |
406 impl.DeclareVars(code) | |
407 | |
408 def ConvertParam(code, impl): | |
409 code << 'if (!%s) {' % impl.ConvertParam() | |
410 with code.Indent(): | |
411 code << 'return -1;' | |
412 code << '}' | |
413 | |
414 code << '{' | |
415 with code.Indent(): | |
416 code << 'ScopedCopyLock copy_lock(nap);' | |
417 # Convert and validate pointers in two passes. | |
418 # Arrays cannot be validated until the size parameter has been | |
419 # converted. | |
420 for impl in impls: | |
421 if not impl.IsArray(): | |
422 ConvertParam(code, impl) | |
423 for impl in impls: | |
424 if impl.IsArray(): | |
425 ConvertParam(code, impl) | |
426 code << '}' | |
427 code << '' | |
428 | |
429 # Call | |
430 getParams = [impl.CallParam() for impl in impls[:-1]] | |
431 callTarget = f.name | |
432 # Redirect to namespaced functions. | |
433 if callTarget.startswith("Mojo"): | |
434 callTarget = callTarget[:4] + 'SystemImpl' + callTarget[4:] | |
435 getParams = ['g_mojo_system'] + getParams | |
436 code << 'result_value = %s(%s);' % (callTarget, ', '.join(getParams)) | |
437 code << '' | |
438 | |
439 # Write outputs | |
440 code << '{' | |
441 with code.Indent(): | |
442 code << 'ScopedCopyLock copy_lock(nap);' | |
443 for impl in impls: | |
444 impl.CopyOut(code) | |
445 code << '}' | |
446 code << '' | |
447 | |
448 code << 'return 0;' | |
449 code << '}' | |
450 | |
451 code.PopMargin() | |
452 | |
453 body = code.GetValue() | |
454 text = template.render( | |
455 body=body, | |
456 **common_vars) | |
457 out.write(text) | |
458 | |
459 | |
460 # A header declaring the IRT interface for accessing Mojo functions. | |
461 def GenerateMojoIrtHeader(functions, common_vars, out): | |
462 template = jinja_env.get_template('mojo_irt.h.tmpl') | |
463 code = CodeWriter() | |
464 | |
465 code << 'struct nacl_irt_mojo {' | |
466 with code.Indent(): | |
467 for f in functions: | |
468 for line in Wrap('%s (*%s)(' % (f.return_type, f.name), | |
469 f.ParamList(), | |
470 ');'): | |
471 code << line | |
472 | |
473 code << '};' | |
474 | |
475 body = code.GetValue() | |
476 | |
477 text = template.render( | |
478 body=body, | |
479 **common_vars) | |
480 out.write(text) | |
481 | |
482 # IRT interface which implements the Mojo public API. | |
483 def GenerateMojoIrtImplementation(functions, common_vars, out): | |
484 template = jinja_env.get_template('mojo_irt.c.tmpl') | |
485 code = CodeWriter() | |
486 | |
487 for f in functions: | |
488 for line in Wrap('static %s irt_%s(' % (f.return_type, f.name), | |
489 f.ParamList(), | |
490 ') {'): | |
491 code << line | |
492 | |
493 # 2 extra parameters: message ID and return value. | |
494 num_params = len(f.params) + 2 | |
495 | |
496 with code.Indent(): | |
497 code << 'uint32_t params[%d];' % num_params | |
498 return_type = f.result_param.base_type | |
499 if return_type == 'MojoResult': | |
500 default = 'MOJO_RESULT_INVALID_ARGUMENT' | |
501 elif return_type == 'MojoTimeTicks': | |
502 default = '0' | |
503 else: | |
504 raise Exception('Unhandled return type: ' + return_type) | |
505 code << '%s %s = %s;' % (return_type, f.result_param.name, default) | |
506 | |
507 # Message ID | |
508 code << 'params[0] = %d;' % f.uid | |
509 # Parameter pointers | |
510 cast_template = 'params[%d] = (uint32_t)(%s);' | |
511 for p in f.params: | |
512 ptr = p.name | |
513 if p.IsPassedByValue(): | |
514 ptr = '&' + ptr | |
515 code << cast_template % (p.uid + 1, ptr) | |
516 # Return value pointer | |
517 code << cast_template % (num_params - 1, '&' + f.result_param.name) | |
518 | |
519 code << 'DoMojoCall(params, sizeof(params));' | |
520 code << 'return %s;' % f.result_param.name | |
521 | |
522 # Add body here. | |
523 code << "};" | |
524 code << "\n" | |
525 | |
526 # Now we've emitted all the functions, but we still need the struct | |
527 # definition. | |
528 code << 'struct nacl_irt_mojo kIrtMojo = {' | |
529 for f in functions: | |
530 with code.Indent(): | |
531 code << '&irt_%s,' % f.name | |
532 code << '};' | |
533 | |
534 body = code.GetValue() | |
535 | |
536 text = template.render( | |
537 body=body, | |
538 **common_vars) | |
539 out.write(text) | |
540 | |
541 | |
542 def OutFile(dir_path, name): | |
543 if not os.path.exists(dir_path): | |
544 try: | |
545 os.makedirs(dir_path) | |
546 except OSError as e: | |
547 # There may have been a race to create this directory. | |
548 if e.errno != errno.EEXIST: | |
549 raise | |
550 full_path = os.path.join(dir_path, name) | |
551 print full_path | |
552 return open(full_path, 'w') | |
553 | |
554 | |
555 def main(): | |
556 root_dir = RepoRoot() | |
557 | |
558 platform_dir = 'mojo/public/platform/nacl' | |
559 bindings_dir = 'mojo/nacl/sfi/nacl_bindings' | |
560 | |
561 full_platform_dir = os.path.join(root_dir, platform_dir) | |
562 full_bindings_dir = os.path.join(root_dir, bindings_dir) | |
563 | |
564 common_vars = dict( | |
565 generator_warning=GeneratorWarning(), | |
566 platform_dir=platform_dir, | |
567 platform_dir_header_path=platform_dir.replace("/", "_").upper(), | |
568 bindings_dir=bindings_dir, | |
569 ) | |
570 | |
571 mojo = interface.MakeInterface() | |
572 | |
573 out = OutFile(full_platform_dir, 'libmojo.cc') | |
574 GenerateLibMojo(mojo.functions, common_vars, out) | |
575 | |
576 out = OutFile(full_bindings_dir, 'mojo_syscall.cc') | |
577 GenerateMojoSyscall(mojo.functions, common_vars, out) | |
578 | |
579 out = OutFile(full_platform_dir, 'mojo_irt.h') | |
580 GenerateMojoIrtHeader(mojo.functions, common_vars, out) | |
581 | |
582 out = OutFile(full_bindings_dir, 'mojo_irt.c') | |
583 GenerateMojoIrtImplementation(mojo.functions, common_vars, out) | |
584 | |
585 if __name__ == '__main__': | |
586 main() | |
OLD | NEW |