1 /*
2 * Copyright (c) 2015 PLUMgrid, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include <fcntl.h>
17 #include <map>
18 #include <string>
19 #include <sys/stat.h>
20 #include <unistd.h>
21 #include <vector>
22 #include <linux/bpf.h>
23
24 #include <llvm/ExecutionEngine/MCJIT.h>
25 #include <llvm/ExecutionEngine/SectionMemoryManager.h>
26 #include <llvm/IR/IRPrintingPasses.h>
27 #include <llvm/IR/LegacyPassManager.h>
28 #include <llvm/IR/LLVMContext.h>
29 #include <llvm/IR/Module.h>
30 #include <llvm/IR/Verifier.h>
31 #include <llvm/Support/TargetSelect.h>
32 #include <llvm/Transforms/IPO.h>
33 #include <llvm/Transforms/IPO/PassManagerBuilder.h>
34 #include <llvm-c/Transforms/IPO.h>
35
36 #include "common.h"
37 #include "bcc_debug.h"
38 #include "frontends/b/loader.h"
39 #include "frontends/clang/loader.h"
40 #include "frontends/clang/b_frontend_action.h"
41 #include "bpf_module.h"
42 #include "exported_files.h"
43 #include "libbpf.h"
44
45 namespace ebpf {
46
47 using std::get;
48 using std::make_tuple;
49 using std::map;
50 using std::move;
51 using std::string;
52 using std::tuple;
53 using std::unique_ptr;
54 using std::vector;
55 using namespace llvm;
56
57 const string BPFModule::FN_PREFIX = BPF_FN_PREFIX;
58
59 // Snooping class to remember the sections as the JIT creates them
60 class MyMemoryManager : public SectionMemoryManager {
61 public:
62
MyMemoryManager(map<string,tuple<uint8_t *,uintptr_t>> * sections)63 explicit MyMemoryManager(map<string, tuple<uint8_t *, uintptr_t>> *sections)
64 : sections_(sections) {
65 }
66
~MyMemoryManager()67 virtual ~MyMemoryManager() {}
allocateCodeSection(uintptr_t Size,unsigned Alignment,unsigned SectionID,StringRef SectionName)68 uint8_t *allocateCodeSection(uintptr_t Size, unsigned Alignment,
69 unsigned SectionID,
70 StringRef SectionName) override {
71 uint8_t *Addr = SectionMemoryManager::allocateCodeSection(Size, Alignment, SectionID, SectionName);
72 //printf("allocateCodeSection: %s Addr %p Size %ld Alignment %d SectionID %d\n",
73 // SectionName.str().c_str(), (void *)Addr, Size, Alignment, SectionID);
74 (*sections_)[SectionName.str()] = make_tuple(Addr, Size);
75 return Addr;
76 }
allocateDataSection(uintptr_t Size,unsigned Alignment,unsigned SectionID,StringRef SectionName,bool isReadOnly)77 uint8_t *allocateDataSection(uintptr_t Size, unsigned Alignment,
78 unsigned SectionID, StringRef SectionName,
79 bool isReadOnly) override {
80 uint8_t *Addr = SectionMemoryManager::allocateDataSection(Size, Alignment, SectionID, SectionName, isReadOnly);
81 //printf("allocateDataSection: %s Addr %p Size %ld Alignment %d SectionID %d RO %d\n",
82 // SectionName.str().c_str(), (void *)Addr, Size, Alignment, SectionID, isReadOnly);
83 (*sections_)[SectionName.str()] = make_tuple(Addr, Size);
84 return Addr;
85 }
86 map<string, tuple<uint8_t *, uintptr_t>> *sections_;
87 };
88
BPFModule(unsigned flags,TableStorage * ts,bool rw_engine_enabled,const std::string & maps_ns)89 BPFModule::BPFModule(unsigned flags, TableStorage *ts, bool rw_engine_enabled,
90 const std::string &maps_ns)
91 : flags_(flags),
92 rw_engine_enabled_(rw_engine_enabled && bpf_module_rw_engine_enabled()),
93 used_b_loader_(false),
94 ctx_(new LLVMContext),
95 id_(std::to_string((uintptr_t)this)),
96 maps_ns_(maps_ns),
97 ts_(ts) {
98 initialize_rw_engine();
99 LLVMInitializeBPFTarget();
100 LLVMInitializeBPFTargetMC();
101 LLVMInitializeBPFTargetInfo();
102 LLVMInitializeBPFAsmPrinter();
103 #if LLVM_MAJOR_VERSION >= 6
104 LLVMInitializeBPFAsmParser();
105 if (flags & DEBUG_SOURCE)
106 LLVMInitializeBPFDisassembler();
107 #endif
108 LLVMLinkInMCJIT(); /* call empty function to force linking of MCJIT */
109 if (!ts_) {
110 local_ts_ = createSharedTableStorage();
111 ts_ = &*local_ts_;
112 }
113 func_src_ = ebpf::make_unique<FuncSource>();
114 }
115
unimplemented_sscanf(const char *,void *)116 static StatusTuple unimplemented_sscanf(const char *, void *) {
117 return StatusTuple(-1, "sscanf unimplemented");
118 }
unimplemented_snprintf(char *,size_t,const void *)119 static StatusTuple unimplemented_snprintf(char *, size_t, const void *) {
120 return StatusTuple(-1, "snprintf unimplemented");
121 }
122
~BPFModule()123 BPFModule::~BPFModule() {
124 for (auto &v : tables_) {
125 v->key_sscanf = unimplemented_sscanf;
126 v->leaf_sscanf = unimplemented_sscanf;
127 v->key_snprintf = unimplemented_snprintf;
128 v->leaf_snprintf = unimplemented_snprintf;
129 }
130
131 if (!rw_engine_enabled_) {
132 for (auto section : sections_)
133 delete[] get<0>(section.second);
134 }
135
136 engine_.reset();
137 cleanup_rw_engine();
138 ctx_.reset();
139 func_src_.reset();
140
141 ts_->DeletePrefix(Path({id_}));
142 }
143
144 // load an entire c file as a module
load_cfile(const string & file,bool in_memory,const char * cflags[],int ncflags)145 int BPFModule::load_cfile(const string &file, bool in_memory, const char *cflags[], int ncflags) {
146 ClangLoader clang_loader(&*ctx_, flags_);
147 if (clang_loader.parse(&mod_, *ts_, file, in_memory, cflags, ncflags, id_,
148 *func_src_, mod_src_, maps_ns_))
149 return -1;
150 return 0;
151 }
152
153 // NOTE: this is a duplicate of the above, but planning to deprecate if we
154 // settle on clang as the frontend
155
156 // Load in a pre-built list of functions into the initial Module object, then
157 // build an ExecutionEngine.
load_includes(const string & text)158 int BPFModule::load_includes(const string &text) {
159 ClangLoader clang_loader(&*ctx_, flags_);
160 if (clang_loader.parse(&mod_, *ts_, text, true, nullptr, 0, "", *func_src_,
161 mod_src_, ""))
162 return -1;
163 return 0;
164 }
165
annotate_light()166 void BPFModule::annotate_light() {
167 for (auto fn = mod_->getFunctionList().begin(); fn != mod_->getFunctionList().end(); ++fn)
168 if (!fn->hasFnAttribute(Attribute::NoInline))
169 fn->addFnAttr(Attribute::AlwaysInline);
170
171 size_t id = 0;
172 Path path({id_});
173 for (auto it = ts_->lower_bound(path), up = ts_->upper_bound(path); it != up; ++it) {
174 TableDesc &table = it->second;
175 tables_.push_back(&it->second);
176 table_names_[table.name] = id++;
177 }
178 }
179
dump_ir(Module & mod)180 void BPFModule::dump_ir(Module &mod) {
181 legacy::PassManager PM;
182 PM.add(createPrintModulePass(errs()));
183 PM.run(mod);
184 }
185
run_pass_manager(Module & mod)186 int BPFModule::run_pass_manager(Module &mod) {
187 if (verifyModule(mod, &errs())) {
188 if (flags_ & DEBUG_LLVM_IR)
189 dump_ir(mod);
190 return -1;
191 }
192
193 legacy::PassManager PM;
194 PassManagerBuilder PMB;
195 PMB.OptLevel = 3;
196 PM.add(createFunctionInliningPass());
197 /*
198 * llvm < 4.0 needs
199 * PM.add(createAlwaysInlinerPass());
200 * llvm >= 4.0 needs
201 * PM.add(createAlwaysInlinerLegacyPass());
202 * use below 'stable' workaround
203 */
204 LLVMAddAlwaysInlinerPass(reinterpret_cast<LLVMPassManagerRef>(&PM));
205 PMB.populateModulePassManager(PM);
206 if (flags_ & DEBUG_LLVM_IR)
207 PM.add(createPrintModulePass(outs()));
208 PM.run(mod);
209 return 0;
210 }
211
finalize()212 int BPFModule::finalize() {
213 Module *mod = &*mod_;
214 std::map<std::string, std::tuple<uint8_t *, uintptr_t>> tmp_sections,
215 *sections_p;
216
217 mod->setTargetTriple("bpf-pc-linux");
218 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
219 mod->setDataLayout("e-m:e-p:64:64-i64:64-n32:64-S128");
220 #else
221 mod->setDataLayout("E-m:e-p:64:64-i64:64-n32:64-S128");
222 #endif
223 sections_p = rw_engine_enabled_ ? §ions_ : &tmp_sections;
224
225 string err;
226 EngineBuilder builder(move(mod_));
227 builder.setErrorStr(&err);
228 builder.setMCJITMemoryManager(ebpf::make_unique<MyMemoryManager>(sections_p));
229 builder.setMArch("bpf");
230 builder.setUseOrcMCJITReplacement(false);
231 engine_ = unique_ptr<ExecutionEngine>(builder.create());
232 if (!engine_) {
233 fprintf(stderr, "Could not create ExecutionEngine: %s\n", err.c_str());
234 return -1;
235 }
236
237 if (flags_ & DEBUG_SOURCE)
238 engine_->setProcessAllSections(true);
239
240 if (int rc = run_pass_manager(*mod))
241 return rc;
242
243 engine_->finalizeObject();
244
245 if (flags_ & DEBUG_SOURCE) {
246 SourceDebugger src_debugger(mod, *sections_p, FN_PREFIX, mod_src_,
247 src_dbg_fmap_);
248 src_debugger.dump();
249 }
250
251 if (!rw_engine_enabled_) {
252 // Setup sections_ correctly and then free llvm internal memory
253 for (auto section : tmp_sections) {
254 auto fname = section.first;
255 uintptr_t size = get<1>(section.second);
256 uint8_t *tmp_p = NULL;
257 // Only copy data for non-map sections
258 if (strncmp("maps/", section.first.c_str(), 5)) {
259 uint8_t *addr = get<0>(section.second);
260 tmp_p = new uint8_t[size];
261 memcpy(tmp_p, addr, size);
262 }
263 sections_[fname] = make_tuple(tmp_p, size);
264 }
265 engine_.reset();
266 ctx_.reset();
267 }
268
269 // give functions an id
270 for (auto section : sections_)
271 if (!strncmp(FN_PREFIX.c_str(), section.first.c_str(), FN_PREFIX.size()))
272 function_names_.push_back(section.first);
273
274 return 0;
275 }
276
num_functions() const277 size_t BPFModule::num_functions() const {
278 return function_names_.size();
279 }
280
function_name(size_t id) const281 const char * BPFModule::function_name(size_t id) const {
282 if (id >= function_names_.size())
283 return nullptr;
284 return function_names_[id].c_str() + FN_PREFIX.size();
285 }
286
function_start(size_t id) const287 uint8_t * BPFModule::function_start(size_t id) const {
288 if (id >= function_names_.size())
289 return nullptr;
290 auto section = sections_.find(function_names_[id]);
291 if (section == sections_.end())
292 return nullptr;
293 return get<0>(section->second);
294 }
295
function_start(const string & name) const296 uint8_t * BPFModule::function_start(const string &name) const {
297 auto section = sections_.find(FN_PREFIX + name);
298 if (section == sections_.end())
299 return nullptr;
300
301 return get<0>(section->second);
302 }
303
function_source(const string & name) const304 const char * BPFModule::function_source(const string &name) const {
305 return func_src_->src(name);
306 }
307
function_source_rewritten(const string & name) const308 const char * BPFModule::function_source_rewritten(const string &name) const {
309 return func_src_->src_rewritten(name);
310 }
311
annotate_prog_tag(const string & name,int prog_fd,struct bpf_insn * insns,int prog_len)312 int BPFModule::annotate_prog_tag(const string &name, int prog_fd,
313 struct bpf_insn *insns, int prog_len) {
314 unsigned long long tag1, tag2;
315 int err;
316
317 err = bpf_prog_compute_tag(insns, prog_len, &tag1);
318 if (err)
319 return err;
320 err = bpf_prog_get_tag(prog_fd, &tag2);
321 if (err)
322 return err;
323 if (tag1 != tag2) {
324 fprintf(stderr, "prog tag mismatch %llx %llx\n", tag1, tag2);
325 return -1;
326 }
327
328 err = mkdir(BCC_PROG_TAG_DIR, 0777);
329 if (err && errno != EEXIST) {
330 fprintf(stderr, "cannot create " BCC_PROG_TAG_DIR "\n");
331 return -1;
332 }
333
334 char buf[128];
335 ::snprintf(buf, sizeof(buf), BCC_PROG_TAG_DIR "/bpf_prog_%llx", tag1);
336 err = mkdir(buf, 0777);
337 if (err && errno != EEXIST) {
338 fprintf(stderr, "cannot create %s\n", buf);
339 return -1;
340 }
341
342 ::snprintf(buf, sizeof(buf), BCC_PROG_TAG_DIR "/bpf_prog_%llx/%s.c",
343 tag1, name.data());
344 FileDesc fd(open(buf, O_CREAT | O_WRONLY | O_TRUNC, 0644));
345 if (fd < 0) {
346 fprintf(stderr, "cannot create %s\n", buf);
347 return -1;
348 }
349
350 const char *src = function_source(name);
351 write(fd, src, strlen(src));
352
353 ::snprintf(buf, sizeof(buf), BCC_PROG_TAG_DIR "/bpf_prog_%llx/%s.rewritten.c",
354 tag1, name.data());
355 fd = open(buf, O_CREAT | O_WRONLY | O_TRUNC, 0644);
356 if (fd < 0) {
357 fprintf(stderr, "cannot create %s\n", buf);
358 return -1;
359 }
360
361 src = function_source_rewritten(name);
362 write(fd, src, strlen(src));
363
364 if (!src_dbg_fmap_[name].empty()) {
365 ::snprintf(buf, sizeof(buf), BCC_PROG_TAG_DIR "/bpf_prog_%llx/%s.dis.txt",
366 tag1, name.data());
367 fd = open(buf, O_CREAT | O_WRONLY | O_TRUNC, 0644);
368 if (fd < 0) {
369 fprintf(stderr, "cannot create %s\n", buf);
370 return -1;
371 }
372
373 const char *src = src_dbg_fmap_[name].c_str();
374 write(fd, src, strlen(src));
375 }
376
377 return 0;
378 }
379
function_size(size_t id) const380 size_t BPFModule::function_size(size_t id) const {
381 if (id >= function_names_.size())
382 return 0;
383 auto section = sections_.find(function_names_[id]);
384 if (section == sections_.end())
385 return 0;
386 return get<1>(section->second);
387 }
388
function_size(const string & name) const389 size_t BPFModule::function_size(const string &name) const {
390 auto section = sections_.find(FN_PREFIX + name);
391 if (section == sections_.end())
392 return 0;
393
394 return get<1>(section->second);
395 }
396
license() const397 char * BPFModule::license() const {
398 auto section = sections_.find("license");
399 if (section == sections_.end())
400 return nullptr;
401
402 return (char *)get<0>(section->second);
403 }
404
kern_version() const405 unsigned BPFModule::kern_version() const {
406 auto section = sections_.find("version");
407 if (section == sections_.end())
408 return 0;
409
410 return *(unsigned *)get<0>(section->second);
411 }
412
num_tables() const413 size_t BPFModule::num_tables() const { return tables_.size(); }
414
table_id(const string & name) const415 size_t BPFModule::table_id(const string &name) const {
416 auto it = table_names_.find(name);
417 if (it == table_names_.end()) return ~0ull;
418 return it->second;
419 }
420
table_fd(const string & name) const421 int BPFModule::table_fd(const string &name) const {
422 return table_fd(table_id(name));
423 }
424
table_fd(size_t id) const425 int BPFModule::table_fd(size_t id) const {
426 if (id >= tables_.size())
427 return -1;
428 return tables_[id]->fd;
429 }
430
table_type(const string & name) const431 int BPFModule::table_type(const string &name) const {
432 return table_type(table_id(name));
433 }
434
table_type(size_t id) const435 int BPFModule::table_type(size_t id) const {
436 if (id >= tables_.size())
437 return -1;
438 return tables_[id]->type;
439 }
440
table_max_entries(const string & name) const441 size_t BPFModule::table_max_entries(const string &name) const {
442 return table_max_entries(table_id(name));
443 }
444
table_max_entries(size_t id) const445 size_t BPFModule::table_max_entries(size_t id) const {
446 if (id >= tables_.size())
447 return 0;
448 return tables_[id]->max_entries;
449 }
450
table_flags(const string & name) const451 int BPFModule::table_flags(const string &name) const {
452 return table_flags(table_id(name));
453 }
454
table_flags(size_t id) const455 int BPFModule::table_flags(size_t id) const {
456 if (id >= tables_.size())
457 return -1;
458 return tables_[id]->flags;
459 }
460
table_name(size_t id) const461 const char * BPFModule::table_name(size_t id) const {
462 if (id >= tables_.size())
463 return nullptr;
464 return tables_[id]->name.c_str();
465 }
466
table_key_desc(size_t id) const467 const char * BPFModule::table_key_desc(size_t id) const {
468 if (used_b_loader_) return nullptr;
469 if (id >= tables_.size())
470 return nullptr;
471 return tables_[id]->key_desc.c_str();
472 }
473
table_key_desc(const string & name) const474 const char * BPFModule::table_key_desc(const string &name) const {
475 return table_key_desc(table_id(name));
476 }
477
table_leaf_desc(size_t id) const478 const char * BPFModule::table_leaf_desc(size_t id) const {
479 if (used_b_loader_) return nullptr;
480 if (id >= tables_.size())
481 return nullptr;
482 return tables_[id]->leaf_desc.c_str();
483 }
484
table_leaf_desc(const string & name) const485 const char * BPFModule::table_leaf_desc(const string &name) const {
486 return table_leaf_desc(table_id(name));
487 }
table_key_size(size_t id) const488 size_t BPFModule::table_key_size(size_t id) const {
489 if (id >= tables_.size())
490 return 0;
491 return tables_[id]->key_size;
492 }
table_key_size(const string & name) const493 size_t BPFModule::table_key_size(const string &name) const {
494 return table_key_size(table_id(name));
495 }
496
table_leaf_size(size_t id) const497 size_t BPFModule::table_leaf_size(size_t id) const {
498 if (id >= tables_.size())
499 return 0;
500 return tables_[id]->leaf_size;
501 }
table_leaf_size(const string & name) const502 size_t BPFModule::table_leaf_size(const string &name) const {
503 return table_leaf_size(table_id(name));
504 }
505
506 struct TableIterator {
TableIteratorebpf::TableIterator507 TableIterator(size_t key_size, size_t leaf_size)
508 : key(new uint8_t[key_size]), leaf(new uint8_t[leaf_size]) {
509 }
510 unique_ptr<uint8_t[]> key;
511 unique_ptr<uint8_t[]> leaf;
512 uint8_t keyb[512];
513 };
514
table_key_printf(size_t id,char * buf,size_t buflen,const void * key)515 int BPFModule::table_key_printf(size_t id, char *buf, size_t buflen, const void *key) {
516 if (id >= tables_.size())
517 return -1;
518 const TableDesc &desc = *tables_[id];
519 StatusTuple rc = desc.key_snprintf(buf, buflen, key);
520 if (rc.code() < 0) {
521 fprintf(stderr, "%s\n", rc.msg().c_str());
522 return -1;
523 }
524 return 0;
525 }
526
table_leaf_printf(size_t id,char * buf,size_t buflen,const void * leaf)527 int BPFModule::table_leaf_printf(size_t id, char *buf, size_t buflen, const void *leaf) {
528 if (id >= tables_.size())
529 return -1;
530 const TableDesc &desc = *tables_[id];
531 StatusTuple rc = desc.leaf_snprintf(buf, buflen, leaf);
532 if (rc.code() < 0) {
533 fprintf(stderr, "%s\n", rc.msg().c_str());
534 return -1;
535 }
536 return 0;
537 }
538
table_key_scanf(size_t id,const char * key_str,void * key)539 int BPFModule::table_key_scanf(size_t id, const char *key_str, void *key) {
540 if (id >= tables_.size())
541 return -1;
542 const TableDesc &desc = *tables_[id];
543 StatusTuple rc = desc.key_sscanf(key_str, key);
544 if (rc.code() < 0) {
545 fprintf(stderr, "%s\n", rc.msg().c_str());
546 return -1;
547 }
548 return 0;
549 }
550
table_leaf_scanf(size_t id,const char * leaf_str,void * leaf)551 int BPFModule::table_leaf_scanf(size_t id, const char *leaf_str, void *leaf) {
552 if (id >= tables_.size())
553 return -1;
554 const TableDesc &desc = *tables_[id];
555 StatusTuple rc = desc.leaf_sscanf(leaf_str, leaf);
556 if (rc.code() < 0) {
557 fprintf(stderr, "%s\n", rc.msg().c_str());
558 return -1;
559 }
560 return 0;
561 }
562
563 // load a B file, which comes in two parts
load_b(const string & filename,const string & proto_filename)564 int BPFModule::load_b(const string &filename, const string &proto_filename) {
565 if (!sections_.empty()) {
566 fprintf(stderr, "Program already initialized\n");
567 return -1;
568 }
569 if (filename.empty() || proto_filename.empty()) {
570 fprintf(stderr, "Invalid filenames\n");
571 return -1;
572 }
573
574 // Helpers are inlined in the following file (C). Load the definitions and
575 // pass the partially compiled module to the B frontend to continue with.
576 auto helpers_h = ExportedFiles::headers().find("/virtual/include/bcc/helpers.h");
577 if (helpers_h == ExportedFiles::headers().end()) {
578 fprintf(stderr, "Internal error: missing bcc/helpers.h");
579 return -1;
580 }
581 if (int rc = load_includes(helpers_h->second))
582 return rc;
583
584 BLoader b_loader(flags_);
585 used_b_loader_ = true;
586 if (int rc = b_loader.parse(&*mod_, filename, proto_filename, *ts_, id_,
587 maps_ns_))
588 return rc;
589 if (rw_engine_enabled_) {
590 if (int rc = annotate())
591 return rc;
592 } else {
593 annotate_light();
594 }
595 if (int rc = finalize())
596 return rc;
597 return 0;
598 }
599
600 // load a C file
load_c(const string & filename,const char * cflags[],int ncflags)601 int BPFModule::load_c(const string &filename, const char *cflags[], int ncflags) {
602 if (!sections_.empty()) {
603 fprintf(stderr, "Program already initialized\n");
604 return -1;
605 }
606 if (filename.empty()) {
607 fprintf(stderr, "Invalid filename\n");
608 return -1;
609 }
610 if (int rc = load_cfile(filename, false, cflags, ncflags))
611 return rc;
612 if (rw_engine_enabled_) {
613 if (int rc = annotate())
614 return rc;
615 } else {
616 annotate_light();
617 }
618 if (int rc = finalize())
619 return rc;
620 return 0;
621 }
622
623 // load a C text string
load_string(const string & text,const char * cflags[],int ncflags)624 int BPFModule::load_string(const string &text, const char *cflags[], int ncflags) {
625 if (!sections_.empty()) {
626 fprintf(stderr, "Program already initialized\n");
627 return -1;
628 }
629 if (int rc = load_cfile(text, true, cflags, ncflags))
630 return rc;
631 if (rw_engine_enabled_) {
632 if (int rc = annotate())
633 return rc;
634 } else {
635 annotate_light();
636 }
637
638 if (int rc = finalize())
639 return rc;
640 return 0;
641 }
642
643 } // namespace ebpf
644