Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add compiler signature/version checking

The verison field in .rbc files now contains a 64bit signature
calculated from the compiler and opcodes. .rbc are validated against
this signature to see if they're valid to use.
  • Loading branch information...
commit 6fa3dc44137f63452d06e9d45e37a04dead665a5 1 parent 5447903
Evan Phoenix authored
View
35 kernel/delta/requirer.rb
@@ -1,12 +1,5 @@
module Rubinius
- ##
- # This const controls what the lowest version of compiled methods we can
- # allow is. This allows us to cut off compability at some point, or just
- # increment when major changes are made to the compiler.
-
- CompiledMethodVersion = 6
-
def self.compile_file(file, save=true)
cm = Rubinius::CompilerNG.compile_file file, 1
@@ -22,6 +15,8 @@ def self.compile_file(file, save=true)
return cm
end
+
+ class InvalidRBC < RuntimeError; end
end
##
@@ -31,20 +26,27 @@ def self.compile_file(file, save=true)
class Requirer
# TODO: This is temporary until the compiler is refactored
def self.version_number
- 42 # No question mark. This is fact.
+ Rubinius::Signature
end
# TODO: This is temporary until the compiler is refactored
module Utils
- @load_rbc_directly = false
-
def self.compiler
return ::Requirer
end
def self.version_number
- return self.compiler ? self.compiler.version_number : 0
+ Rubinius::Signature
+ end
+
+ @load_rbc_directly = false
+ def self.loading_rbc_only(val=true)
+ old = @load_rbc_directly
+ @load_rbc_directly = val
+ yield
+ ensure
+ @load_rbc_directly = old
end
def self.execute(string)
@@ -65,7 +67,8 @@ def self.__unexpected_break__
def self.load_from_rbc(path, version)
Ruby.primitive :compiledfile_load
- raise PrimitiveFailure, "CompiledFile.load_from_rbc primitive failed"
+
+ raise Rubinius::InvalidRBC, path
# HACK: remove the primitive above when compiled_file.rb
# unmarshal_data method is fixed and ruby performance is better
@@ -178,8 +181,9 @@ def self.single_load(dir, rb, rbc, ext, requiring, options)
if @load_rbc_directly
if rbc_stat and rbc_stat.file?
+ ver = (@load_rbc_directly == :force ? 0 : version_number)
compile_feature(rb, requiring) do
- cm = load_from_rbc(rbc_path, version_number)
+ cm = load_from_rbc(rbc_path, ver)
raise LoadError, "Invalid .rbc: #{rbc_path}" unless cm
end
else
@@ -217,6 +221,11 @@ def self.single_load(dir, rb, rbc, ext, requiring, options)
compile_feature(rb, requiring) do
begin
cm = load_from_rbc(rbc_path, version_number)
+ rescue InvalidRBC
+ if $DEBUG_LOADING
+ STDERR.puts "[Invalid .rbc #{rbc_path} detected, ignoring]"
+ end
+ cm = nil
rescue TypeError, IOError
cm = nil
end
View
39 kernel/loader.rb
@@ -300,6 +300,13 @@ def options(argv=ARGV)
puts
end
+ # This will only trigger if it's not the first option, in which case
+ # we'll just tell the user to make it the first option.
+ options.on "--rebuild-compiler", "Rebuild the Rubinius compiler" do
+ puts "This must be the first and only option."
+ exit 1
+ end
+
options.doc <<-DOC
\nVM Options
-X<variable>[=<value>]
@@ -329,7 +336,37 @@ def load_paths
end
def load_compiler
- require "compiler"
+ # This happens before we parse ARGV, so we have to check ARGV ourselves
+ # here.
+
+ rebuild = (ARGV.last == "--rebuild-compiler")
+
+ begin
+ Requirer::Utils.loading_rbc_only(rebuild ? :force : true) do
+ require "compiler"
+ end
+ rescue Rubinius::InvalidRBC => e
+ STDERR.puts "There was an error loading the compiler."
+ STDERR.puts "It appears that your compiler is out of date with the VM."
+ STDERR.puts "\nPlease use 'rbx --rebuild-compiler' or 'rake [instal]' to"
+ STDERR.puts "bring the compiler back to date."
+ exit 1
+ end
+
+ if rebuild
+ STDOUT.puts "Rebuilding compiler..."
+ files =
+ ["#{@main_lib}/compiler.rb"] +
+ Dir["#{@main_lib}/compiler/*.rb"] +
+ Dir["#{@main_lib}/compiler/**/*.rb"]
+
+ files.each do |file|
+ puts "#{file}"
+ Rubinius.compile_file file, true
+ end
+
+ exit 0
+ end
end
# Require any -r arguments
View
2  lib/compiler/compiled_file.rb
@@ -37,7 +37,7 @@ def self.load(stream)
# Writes the CompiledFile +cm+ to +file+.
def self.dump(cm, file)
File.open(file, "w") do |f|
- new("!RBIX", 0, "x").encode_to(f, cm)
+ new("!RBIX", Rubinius::Signature, "x").encode_to(f, cm)
end
rescue Errno::EACCES
# just skip writing the compiled file if we don't have permissions
View
2  lib/compiler/generator.rb
@@ -163,6 +163,8 @@ def package(klass)
cm
end
+ # Replace all Labels in the stream by dereferencing them to their integer
+ # position.
def set_label_positions
@stream.each_with_index do |op, index|
if op.kind_of? Label
View
12 rakelib/kernel.rake
@@ -89,7 +89,7 @@ compiler_files = FileList[
compiler_files.each do |rb|
rbc = rb + "c"
- file rbc => rb
+ file rbc => [rb, compiler_signature]
runtime << rbc
end
@@ -113,11 +113,19 @@ file compiler_signature => compiler_files + parser_ext_files do |t|
end
end
+ # Collapse the digest to a 64bit quantity
+ hd = digest.hexdigest
+ hash = hd[0, 16].to_i(16) ^ hd[16,16].to_i(16) ^ hd[32,8].to_i(16)
+
File.open t.name, "w" do |file|
file.puts "# This file is generated by rakelib/kernel.rake. The signature"
file.puts "# is used to ensure that only current .rbc files are loaded."
file.puts "#"
- file.puts "Rubinius::Signature = 0x#{digest.to_s[0, 16]}"
+ file.puts "Rubinius::Signature = #{hash}"
+ end
+
+ File.open "runtime/signature", "w" do |file|
+ file.puts hash
end
end
View
15 vm/builtin/system.cpp
@@ -77,23 +77,24 @@ namespace rubinius {
//
// HACK: remove this when performance is better and compiled_file.rb
// unmarshal_data method works.
- Object* System::compiledfile_load(STATE, String* path, Object* version) {
+ Object* System::compiledfile_load(STATE, String* path, Integer* version) {
if(!state->probe->nil_p()) {
state->probe->load_runtime(state, std::string(path->c_str()));
}
std::ifstream stream(path->c_str());
if(!stream) {
- std::ostringstream msg;
- msg << "unable to open file to run: " << path->c_str();
- Exception::io_error(state, msg.str().c_str());
+ return Primitives::failure();
}
CompiledFile* cf = CompiledFile::load(stream);
if(cf->magic != "!RBIX") {
- std::ostringstream msg;
- msg << "Invalid file: " << path->c_str();
- Exception::io_error(state, msg.str().c_str());
+ return Primitives::failure();
+ }
+
+ uint64_t ver = version->to_ulong_long();
+ if(ver > 0 && cf->version > 0 && cf->version != ver) {
+ return Primitives::failure();
}
Object *body = cf->body(state);
View
2  vm/builtin/system.hpp
@@ -34,7 +34,7 @@ namespace rubinius {
/** Load a compiled file. */
// Ruby.primitive :compiledfile_load
- static Object* compiledfile_load(STATE, String* path, Object* version);
+ static Object* compiledfile_load(STATE, String* path, Integer* version);
/**
* When running under GDB, stop here.
View
2  vm/compiled_file.cpp
@@ -20,7 +20,7 @@
namespace rubinius {
CompiledFile* CompiledFile::load(std::istream& stream) {
std::string magic, sum;
- long ver;
+ uint64_t ver;
stream >> magic;
stream >> ver;
View
14 vm/compiled_file.hpp
@@ -13,18 +13,20 @@ namespace rubinius {
class CompiledFile {
public:
std::string magic;
- long version;
+ uint64_t version;
std::string sum;
private:
std::istream* stream;
public:
- CompiledFile(std::string magic, long version, std::string sum,
- std::istream* stream) :
- magic(magic), version(version), sum(sum),
- stream(stream) { }
-
+ CompiledFile(std::string magic, uint64_t version, std::string sum,
+ std::istream* stream)
+ : magic(magic)
+ , version(version)
+ , sum(sum)
+ , stream(stream)
+ {}
static CompiledFile* load(std::istream& stream);
Object* body(STATE);
View
17 vm/drivers/cli.cpp
@@ -89,13 +89,22 @@ int main(int argc, char** argv) {
std::cout << "Ruby backtrace:" << std::endl;
env.state->print_backtrace();
- } catch(std::runtime_error& e) {
- std::cout << "Runtime exception: " << e.what() << std::endl;
+ } catch(BadKernelFile& e) {
+ std::cout << "ERROR: BadKernelFile: " << e.what() << "\n\n";
+ std::cout << "An invalid kernel file has been detected.\n";
+ std::cout << "This is because the VM is out of sync with the kernel.\n";
+ std::cout << "Please recompile your kernel using:\n";
+ std::cout << " rake kernel:clean clean\n";
+ std::cout << " rake\n";
+ std::cout << "\nIf the problem persists, please open an issue at:\n";
+ std::cout << " http://github.com/evanphx/rubinius\n";
+ std::cout << "\nThanks,\n Management.\n";
+ return 1;
} catch(VMException &e) {
std::cout << "Unknown VM exception detected." << std::endl;
e.print_backtrace();
- } catch(std::string e) {
- std::cout << e << std::endl;
+ } catch(std::runtime_error& e) {
+ std::cout << "Runtime exception: " << e.what() << std::endl;
} catch(...) {
std::cout << "Unknown exception detected." << std::endl;
}
View
21 vm/environment.cpp
@@ -42,6 +42,7 @@ namespace rubinius {
Environment::Environment(int argc, char** argv)
: argc_(argc)
, argv_(argv)
+ , signature_(0)
, agent(0)
{
#ifdef ENABLE_LLVM
@@ -331,6 +332,9 @@ namespace rubinius {
CompiledFile* cf = CompiledFile::load(stream);
if(cf->magic != "!RBIX") throw std::runtime_error("Invalid file");
+ if(signature_ > 0) {
+ if(cf->version != signature_) throw BadKernelFile(file);
+ }
/** @todo Redundant? CompiledFile::execute() does this. --rue */
state->thread_state()->clear_exception(true);
@@ -413,8 +417,25 @@ namespace rubinius {
// Load the ruby file to prepare for bootstrapping Ruby!
// The bootstrapping for the VM is already done by the time we're here.
+
+ // First, pull in the signature file. This helps control when .rbc files need
+ // to be discarded.
+
+ std::string sig_path = root + "/signature";
+ std::ifstream sig_stream(sig_path.c_str());
+ if(sig_stream) {
+ sig_stream >> signature_;
+ state->globals.rubinius->set_const(state, "Signature",
+ Integer::from(state, signature_));
+ sig_stream.close();
+ } else {
+ state->globals.rubinius->set_const(state, "Signature", Integer::from(state, 0));
+ }
+
+ // Load alpha
run_file(root + "/alpha.rbc");
+ // Read the index and load the directories listed.
while(!stream.eof()) {
std::string line;
View
10 vm/environment.hpp
@@ -13,6 +13,14 @@ namespace rubinius {
class ConfigParser;
class QueryAgent;
+ // throw when there is a bad signature on a kernel .rbc file.
+ class BadKernelFile : public std::runtime_error {
+ public:
+ BadKernelFile(const std::string& str)
+ : std::runtime_error(str)
+ {}
+ };
+
class Environment {
// The thread used to trigger preemptive thread switching
pthread_t preemption_thread_;
@@ -20,6 +28,8 @@ namespace rubinius {
int argc_;
char** argv_;
+ uint64_t signature_;
+
public:
SharedState* shared;
VM* state;
View
2  vm/test/test_compiled_file.hpp
@@ -32,7 +32,7 @@ class TestCompiledFile : public CxxTest::TestSuite, public VMTest {
CompiledFile* cf = CompiledFile::load(stream);
TS_ASSERT_EQUALS(cf->magic, std::string("!RBIX"));
- TS_ASSERT_EQUALS(cf->version, 1);
+ TS_ASSERT_EQUALS(cf->version, 1ULL);
TS_ASSERT_EQUALS(cf->sum, std::string("aoeu"));
TS_ASSERT_EQUALS((long)stream.tellg(), 13);
}
Please sign in to comment.
Something went wrong with that request. Please try again.