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