/* * click-devirtualize.cc -- virtual function eliminator for Click routers * Eddie Kohler * * Copyright (c) 2000 Massachusetts Institute of Technology * Copyright (c) 2000 Mazu Networks, Inc. * Copyright (c) 2006 Regents of the University of California * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, subject to the conditions * listed in the Click LICENSE file. These conditions include: you must * preserve this copyright notice, and you cannot mention the copyright * holders in advertising related to the Software without their permission. * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This * notice is a summary of the Click LICENSE file; the license in that file is * legally binding. */ #include #include #include #include #include #include "lexert.hh" #include "routert.hh" #include "toolutils.hh" #include "elementmap.hh" #include "cxxclass.hh" #include "md5.h" #include #include "specializer.hh" #include "signature.hh" #include #include #include #include #include #include #define HELP_OPT 300 #define VERSION_OPT 301 #define CLICKPATH_OPT 302 #define ROUTER_OPT 303 #define EXPRESSION_OPT 304 #define OUTPUT_OPT 305 #define KERNEL_OPT 306 #define USERLEVEL_OPT 307 #define SOURCE_OPT 308 #define CONFIG_OPT 309 #define NO_DEVIRTUALIZE_OPT 310 #define DEVIRTUALIZE_OPT 311 #define INSTRS_OPT 312 #define REVERSE_OPT 313 static Clp_Option options[] = { { "clickpath", 'C', CLICKPATH_OPT, Clp_ArgString, 0 }, { "config", 'c', CONFIG_OPT, 0, Clp_Negate }, { "devirtualize", 0, DEVIRTUALIZE_OPT, Clp_ArgString, Clp_Negate }, { "expression", 'e', EXPRESSION_OPT, Clp_ArgString, 0 }, { "file", 'f', ROUTER_OPT, Clp_ArgString, 0 }, { "help", 0, HELP_OPT, 0, 0 }, { 0, 'n', NO_DEVIRTUALIZE_OPT, Clp_ArgString, 0 }, { "kernel", 'k', KERNEL_OPT, 0, Clp_Negate }, { "instructions", 'i', INSTRS_OPT, Clp_ArgString, 0 }, { "output", 'o', OUTPUT_OPT, Clp_ArgString, 0 }, { "reverse", 'r', REVERSE_OPT, 0, Clp_Negate }, { "source", 's', SOURCE_OPT, 0, Clp_Negate }, { "user", 'u', USERLEVEL_OPT, 0, Clp_Negate }, { "version", 'v', VERSION_OPT, 0, 0 } }; static const char *program_name; String click_to_cxx_name(const String &click) { StringAccum sa; const char *s = click.data(); const char *end_s = s + click.length(); for (; s < end_s; s++) if (*s == '_') sa << "_u"; else if (*s == '@') sa << "_a"; else if (*s == '/') sa << "_s"; else sa << *s; return sa.take_string(); } String specialized_click_name(ElementT *e) { return e->type_name() + "@@" + e->name(); } static void parse_instruction(const String &text, Signatures &sigs, ErrorHandler *errh) { Vector words; cp_spacevec(text, words); if (words.size() == 0 || words[0].data()[0] == '#') /* nada */; else if (words[0] == "like") { if (words.size() < 3) errh->error("too few arguments to 'like'"); } else if (words[0] == "noclass") { if (words.size() < 2) errh->error("too few arguments to 'noclass'"); for (int i = 1; i < words.size(); i++) sigs.specialize_class(words[i], 0); } else errh->error("unknown command '%s'", words[0].c_str()); } static void parse_instruction_file(const char *filename, Signatures &sigs, ErrorHandler *errh) { String text = file_string(filename, errh); const char *s = text.data(); int pos = 0; int len = text.length(); while (pos < len) { int pos1 = pos; while (pos < len && s[pos] != '\n' && s[pos] != '\r') pos++; parse_instruction(text.substring(pos1, pos - pos1), sigs, errh); while (pos < len && (s[pos] == '\n' || s[pos] == '\r')) pos++; } } static void reverse_transformation(RouterT *r, ErrorHandler *) { // parse fastclassifier_config if (r->archive_index("devirtualize_info") < 0) return; ArchiveElement &fc_ae = r->archive("devirtualize_info"); Vector new_click_names, old_click_names; parse_tabbed_lines(fc_ae.data, &new_click_names, &old_click_names, (void *)0); // prepare type_index_map : type -> configuration # HashMap new_type_map(-1); Vector old_class; for (int i = 0; i < new_click_names.size(); i++) { new_type_map.insert(ElementClassT::base_type(new_click_names[i]), old_class.size()); old_class.push_back(ElementClassT::base_type(old_click_names[i])); } // change configuration for (int i = 0; i < r->nelements(); i++) { ElementT *e = r->element(i); int nnm = new_type_map[e->type()]; if (nnm >= 0) e->set_type(old_class[nnm]); } // remove requirements { Vector requirements = r->requirements(); for (int i = 0; i < requirements.size(); i++) if (requirements[i].substring(0, 12) == "devirtualize") r->remove_requirement(requirements[i]); } // remove archive elements for (int i = 0; i < r->narchive(); i++) { ArchiveElement &ae = r->archive(i); if (ae.name.substring(0, 12) == "devirtualize" || ae.name == "elementmap-devirtualize.xml") ae.name = String(); } } void short_usage() { fprintf(stderr, "Usage: %s [OPTION]... [ROUTERFILE]\n\ Try '%s --help' for more information.\n", program_name, program_name); } void usage() { printf("\ 'Click-devirtualize' transforms a router configuration by removing virtual\n\ function calls from its elements' source code. The resulting configuration has\n\ both Click-language files and object files.\n\ \n\ Usage: %s [OPTION]... [ROUTERFILE]\n\ \n\ Options:\n\ -f, --file FILE Read router configuration from FILE.\n\ -e, --expression EXPR Use EXPR as router configuration.\n\ -o, --output FILE Write output to FILE.\n\ -k, --kernel Compile into Linux kernel binary package.\n\ -u, --user Compile into user-level binary package.\n\ -s, --source Write source code only.\n\ -c, --config Write new configuration only.\n\ -r, --reverse Reverse devirtualization.\n\ -n, --no-devirtualize CLASS Don't devirtualize element class CLASS.\n\ -i, --instructions FILE Read devirtualization instructions from FILE.\n\ -C, --clickpath PATH Use PATH for CLICKPATH.\n\ --help Print this message and exit.\n\ -v, --version Print version number and exit.\n\ \n\ Report bugs to .\n", program_name); } int main(int argc, char **argv) { click_static_initialize(); CLICK_DEFAULT_PROVIDES; ErrorHandler *errh = ErrorHandler::default_handler(); ErrorHandler *p_errh = new PrefixErrorHandler(errh, "click-devirtualize: "); // read command line arguments Clp_Parser *clp = Clp_NewParser(argc, argv, sizeof(options) / sizeof(options[0]), options); Clp_SetOptionChar(clp, '+', Clp_ShortNegated); program_name = Clp_ProgramName(clp); const char *router_file = 0; bool file_is_expr = false; const char *output_file = 0; int source_only = 0; int config_only = 0; int compile_kernel = 0; int compile_user = 0; int reverse = 0; Vector instruction_files; HashMap specializing; while (1) { int opt = Clp_Next(clp); switch (opt) { case HELP_OPT: usage(); exit(0); break; case VERSION_OPT: printf("click-devirtualize (Click) %s\n", CLICK_VERSION); printf("Copyright (c) 2000 Massachusetts Institute of Technology\n\ Copyright (c) 2000 Mazu Networks, Inc.\n\ This is free software; see the source for copying conditions.\n\ There is NO warranty, not even for merchantability or fitness for a\n\ particular purpose.\n"); exit(0); break; case CLICKPATH_OPT: set_clickpath(clp->arg); break; case ROUTER_OPT: case EXPRESSION_OPT: case Clp_NotOption: if (router_file) { p_errh->error("router configuration specified twice"); goto bad_option; } router_file = clp->arg; file_is_expr = (opt == EXPRESSION_OPT); break; case OUTPUT_OPT: if (output_file) { p_errh->error("output file specified twice"); goto bad_option; } output_file = clp->arg; break; case SOURCE_OPT: source_only = !clp->negated; break; case CONFIG_OPT: config_only = !clp->negated; break; case KERNEL_OPT: compile_kernel = !clp->negated; break; case USERLEVEL_OPT: compile_user = !clp->negated; break; case DEVIRTUALIZE_OPT: specializing.insert(clp->arg, !clp->negated); break; case NO_DEVIRTUALIZE_OPT: specializing.insert(clp->arg, 0); break; case INSTRS_OPT: instruction_files.push_back(clp->arg); break; case REVERSE_OPT: reverse = !clp->negated; break; bad_option: case Clp_BadOption: short_usage(); exit(1); break; case Clp_Done: goto done; } } done: if (config_only) compile_kernel = compile_user = 0; // read router RouterT *router = read_router(router_file, file_is_expr, errh); if (router) router->flatten(errh); if (!router || errh->nerrors() > 0) exit(1); // open output file FILE *outf = stdout; if (output_file && strcmp(output_file, "-") != 0) { outf = fopen(output_file, "w"); if (!outf) errh->fatal("%s: %s", output_file, strerror(errno)); } // handle reversing case if (reverse) { reverse_transformation(router, errh); write_router_file(router, outf, errh); exit(0); } // find and parse elementmap ElementMap full_elementmap; full_elementmap.parse_all_files(router, CLICK_DATADIR, errh); // initialize signatures Signatures sigs(router); // follow instructions embedded in router definition ElementClassT *devirtualize_info_class = ElementClassT::base_type("DevirtualizeInfo"); for (RouterT::type_iterator x = router->begin_elements(devirtualize_info_class); x; x++) { Vector args; cp_argvec(x->configuration(), args); for (int j = 0; j < args.size(); j++) parse_instruction(args[j], sigs, p_errh); } // follow instructions from command line { for (int i = 0; i < instruction_files.size(); i++) parse_instruction_file(instruction_files[i], sigs, errh); for (StringMap::iterator iter = specializing.begin(); iter; iter++) sigs.specialize_class(iter.key(), iter.value()); } // choose driver for output full_elementmap.check_completeness(router, p_errh); String suffix = ""; String driver_requirement = ""; if (!full_elementmap.driver_indifferent(router)) { bool linuxmodule_ok = full_elementmap.driver_compatible (router, Driver::LINUXMODULE); bool userlevel_ok = full_elementmap.driver_compatible (router, Driver::USERLEVEL); if (linuxmodule_ok && userlevel_ok && (compile_kernel > 0) == (compile_user > 0)) p_errh->fatal("kernel and user-level drivers require different code;\nyou must specify either '-k' or '-u'"); else if (!linuxmodule_ok && compile_kernel > 0) p_errh->fatal("configuration incompatible with kernel driver"); else if (!userlevel_ok && compile_user > 0) p_errh->fatal("configuration incompatible with user-level driver"); else if (compile_kernel > 0 || (linuxmodule_ok && compile_user <= 0)) { suffix = ".k"; driver_requirement = "linuxmodule "; full_elementmap.set_driver(Driver::LINUXMODULE); } else { suffix = ".u"; driver_requirement = "userlevel "; full_elementmap.set_driver(Driver::USERLEVEL); } } // analyze signatures to determine specialization sigs.analyze(full_elementmap); // initialize specializer Specializer specializer(router, full_elementmap); specializer.specialize(sigs, errh); // quit early if nothing was done if (specializer.nspecials() == 0) { if (source_only) errh->message("nothing to devirtualize"); else write_router_file(router, outf, errh); exit(0); } // find name of package String package_name; { md5_state_t pms; char buf[MD5_TEXT_DIGEST_SIZE]; String s = router->configuration_string(); md5_init(&pms); md5_append(&pms, (const md5_byte_t *) s.data(), s.length()); md5_final_text(&pms, buf); package_name = "clickdv_" + String(buf); } router->add_requirement(package_name); // output StringAccum header, source; source << "/** click-compile: -w -fno-access-control */\n"; header << "#ifndef CLICK_" << package_name << "_HH\n" << "#define CLICK_" << package_name << "_HH\n" << "#include \n#include \n"; specializer.output_package(package_name, source, errh); specializer.output(header, source); header << "#endif\n"; // output source code if required if (source_only) { fwrite(header.data(), 1, header.length(), outf); fwrite(source.data(), 1, source.length(), outf); fclose(outf); exit(0); } // create temporary directory String tmpdir; if (compile_user > 0 || compile_kernel > 0) { if (!(tmpdir = click_mktmpdir(errh))) exit(1); // find Click binaries String click_buildtool_prog = clickpath_find_file("click-buildtool", "bin", CLICK_BINDIR, errh); // write header file String hh_filename = package_name + suffix + ".hh"; FILE *f = fopen((tmpdir + hh_filename).c_str(), "w"); if (!f) errh->fatal("%s: %s", (tmpdir + hh_filename).c_str(), strerror(errno)); fwrite(header.data(), 1, header.length(), f); fclose(f); // write C++ file String cxx_filename = package_name + suffix + "_.cc"; f = fopen((tmpdir + cxx_filename).c_str(), "w"); if (!f) errh->fatal("%s: %s", (tmpdir + cxx_filename).c_str(), strerror(errno)); fwrite(source.data(), 1, source.length(), f); fclose(f); // write any archived headers const Vector &aelist = router->archive(); for (int i = 0; i < aelist.size(); i++) if (aelist[i].name.substring(-3) == ".hh") { String filename = tmpdir + aelist[i].name; f = fopen(filename.c_str(), "w"); if (!f) errh->warning("%s: %s", filename.c_str(), strerror(errno)); else { fwrite(aelist[i].data.data(), 1, aelist[i].data.length(), f); fclose(f); } } // compile kernel module if (compile_kernel > 0) { StringAccum compile_command; compile_command << click_buildtool_prog << " makepackage -C " << tmpdir << " -t linuxmodule " << package_name << " " << cxx_filename << " 1>&2"; int compile_retval = system(compile_command.c_str()); if (compile_retval == 127) errh->fatal("could not run '%s'", compile_command.c_str()); else if (compile_retval < 0) errh->fatal("could not run '%s': %s", compile_command.c_str(), strerror(errno)); else if (compile_retval != 0) errh->fatal("'%s' failed", compile_command.c_str()); } // compile userlevel if (compile_user > 0) { StringAccum compile_command; compile_command << click_buildtool_prog << " makepackage -C " << tmpdir << " -t userlevel " << package_name << " " << cxx_filename << " 1>&2"; int compile_retval = system(compile_command.c_str()); if (compile_retval == 127) errh->fatal("could not run '%s'", compile_command.c_str()); else if (compile_retval < 0) errh->fatal("could not run '%s': %s", compile_command.c_str(), strerror(errno)); else if (compile_retval != 0) errh->fatal("'%s' failed", compile_command.c_str()); } } // retype elements specializer.fix_elements(); // read .cc and .?o files, add them to archive { ArchiveElement ae = init_archive_element(package_name + suffix + ".cc", 0600); ae.data = source.take_string(); router->add_archive(ae); ae.name = package_name + suffix + ".hh"; ae.data = header.take_string(); router->add_archive(ae); if (compile_kernel > 0) { ae.name = package_name + ".ko"; ae.data = file_string(tmpdir + ae.name, errh); router->add_archive(ae); } if (compile_user > 0) { ae.name = package_name + ".uo"; ae.data = file_string(tmpdir + ae.name, errh); router->add_archive(ae); } } // add elementmap to archive { if (router->archive_index("elementmap-devirtualize.xml") < 0) router->add_archive(init_archive_element("elementmap-devirtualize.xml", 0600)); ArchiveElement &ae = router->archive("elementmap-devirtualize.xml"); ElementMap em(ae.data); specializer.output_new_elementmap(full_elementmap, em, package_name + suffix, driver_requirement); ae.data = em.unparse("devirtualize"); } // add devirtualize_info to archive { if (router->archive_index("devirtualize_info") < 0) router->add_archive(init_archive_element("devirtualize_info", 0600)); ArchiveElement &ae = router->archive("devirtualize_info"); StringAccum sa; for (int i = 0; i < specializer.nspecials(); i++) { const SpecializedClass &c = specializer.special(i); if (c.special()) sa << c.click_name << '\t' << c.old_click_name << '\n'; } ae.data += sa.take_string(); } // write configuration if (config_only) { String s = router->configuration_string(); fwrite(s.data(), 1, s.length(), outf); } else write_router_file(router, outf, errh); exit(0); }