diff --git a/zircon/docs/kernel_cmdline.md b/zircon/docs/kernel_cmdline.md
index c80d609c628f317ec8ea6a29e84f9e4ca69f88f0..1d15c6284ed4440e5e848b05e874402edac13807 100644
--- a/zircon/docs/kernel_cmdline.md
+++ b/zircon/docs/kernel_cmdline.md
@@ -24,9 +24,14 @@ If this option is set, the system will not use Address Space Layout
 Randomization.
 
 ## bootsvc.next=\<bootfs path\>
+
 Controls what program is executed by bootsvc to continue the boot process.
 If this is not specified, the default next program will be used.
 
+Arguments to the program can optionally be specified using a comma separator
+between the program and individual arguments. For example,
+'bootsvc.next=bin/mybin,arg1,arg2'.
+
 ## devmgr\.epoch=\<seconds\>
 
 Sets the initial offset (from the Unix epoch, in seconds) for the UTC clock.
diff --git a/zircon/scripts/run-zircon b/zircon/scripts/run-zircon
index c3026edbb5e9cb72817a93906a31e779ad7d7970..948904279c4f5eedcfe6728ae0fb11a4042d31d0 100755
--- a/zircon/scripts/run-zircon
+++ b/zircon/scripts/run-zircon
@@ -422,8 +422,6 @@ CMDLINE+="kernel.entropy-mixin=$(head -c 32 /dev/urandom | shasum -a 256 | awk '
 # Don't 'reboot' the emulator if the kernel crashes
 CMDLINE+="kernel.halt-on-panic=true "
 
-CMDLINE="`echo "$CMDLINE" | sed 's/,/,,/g'`"
-
 # run qemu
 echo CMDLINE: $CMDLINE
 cd /
diff --git a/zircon/system/core/bootsvc/TESTING b/zircon/system/core/bootsvc/TESTING
index 7b0ab9d8cb1ca44f83e82c7811c89996cd0716c0..9598538f4cccb718f1ad6c5cd83c290f34387115 100644
--- a/zircon/system/core/bootsvc/TESTING
+++ b/zircon/system/core/bootsvc/TESTING
@@ -1,7 +1,10 @@
 To run the bootsvc integration tests, boot with these boot cmdline arguments:
-`userboot=bin/bootsvc bootsvc.next=bin/bootsvc-tests`.  This will tell bootsvc
-to execute its test suite instead of the usual boot chain.  The expected output
-from a boot in this mode is a unittest suite report that says 0 failed tests.
+
+`userboot=bin/bootsvc bootsvc.next=bin/bootsvc-tests,testargument`.
+
+This will tell bootsvc to execute its test suite instead of the usual boot
+chain. The expected output from a boot in this mode is a unittest suite report
+that says 0 failed tests.
 
 To test that crashlogs are being added to bootfs, you need to add a CRASHLOG item
 to the boot zbi.  For example, to add the contents of README.md as the crashlog:
diff --git a/zircon/system/core/bootsvc/integration-test.cpp b/zircon/system/core/bootsvc/integration-test.cpp
index 24dc2dcc638f51fdf8ce144166cfb718714c2bda..92f6ade329c0e6efd380d4c325816f44ea6c905c 100644
--- a/zircon/system/core/bootsvc/integration-test.cpp
+++ b/zircon/system/core/bootsvc/integration-test.cpp
@@ -26,6 +26,17 @@
 
 #include "util.h"
 
+static fbl::Vector<fbl::String> arguments;
+
+int main(int argc, char** argv) {
+    // Copy arguments for later use in tests.
+    for (int i = 0; i < argc; ++i) {
+        arguments.push_back(fbl::String(argv[i]));
+    }
+
+    return unittest_run_all_tests(argc, argv) ? 0 : -1;
+}
+
 namespace {
 
 constexpr char kArgumentsPath[] = "/bootsvc/" fuchsia_boot_Arguments_Name;
@@ -44,7 +55,7 @@ bool TestLoader() {
     END_TEST;
 }
 
-// Make sure that bootsvc gave us a namespace with only /boot
+// Make sure that bootsvc gave us a namespace with only /boot and /bootsvc.
 bool TestNamespace() {
     BEGIN_TEST;
 
@@ -57,13 +68,73 @@ bool TestNamespace() {
     }
 
     ASSERT_EQ(ns->count, 2);
-    ASSERT_STR_EQ(ns->path[0], "/boot");
-    ASSERT_STR_EQ(ns->path[1], "/bootsvc");
+    EXPECT_STR_EQ(ns->path[0], "/boot");
+    EXPECT_STR_EQ(ns->path[1], "/bootsvc");
 
     free(ns);
     END_TEST;
 }
 
+// Make sure that bootsvc passed along program arguments from bootsvc.next
+// correctly.
+//
+// As documented in TESTING, this test relies on these tests being run by using
+// a boot cmdline that includes 'bootsvc.next=bin/bootsvc-tests,testargument' so
+// that we can test the parsing on bootsvc.next.
+bool TestArguments() {
+    BEGIN_TEST;
+
+    ASSERT_EQ(arguments.size(), 2);
+    EXPECT_STR_EQ(arguments[0].c_str(), "bin/bootsvc-tests");
+    EXPECT_STR_EQ(arguments[1].c_str(), "testargument");
+
+    END_TEST;
+}
+
+struct SplitStringTestCase {
+    const char* input;
+    char delimiter;
+};
+
+// Test the SplitString util individually.
+bool TestSplitString() {
+    BEGIN_TEST;
+
+    // Makeshift parametrized test.
+    struct {
+        const char* input;
+        char delimiter;
+    } cases[] = {
+        {.input = "", .delimiter = ','},
+        {.input = "abcd", .delimiter = ','},
+        {.input = "a,b c,d", .delimiter = ','},
+        {.input = "a,b c,d", .delimiter = ' '},
+        {.input = "::a:", .delimiter = ':'},
+    };
+    fbl::Vector<fbl::String> expected[] = {
+        {},
+        {"abcd"},
+        {"a", "b c", "d"},
+        {"a,b", "c,d"},
+        {"", "", "a", ""},
+    };
+    for (size_t n = 0; n < sizeof(cases) / sizeof(*cases); ++n) {
+        fbl::String case_msg = fbl::StringPrintf("Test Case %zu", n);
+        auto result = bootsvc::SplitString(cases[n].input, cases[n].delimiter);
+
+        // We use EXPECT_EQ rather than ASSERT_EQ so that we can continue with
+        // other test cases if one fails.
+        EXPECT_EQ(result.size(), expected[n].size(), case_msg.c_str());
+        if (result.size() == expected[n].size()) {
+            for (size_t i = 0; i < result.size(); ++i) {
+                EXPECT_STR_EQ(result[i].c_str(), expected[n][i].c_str(), case_msg.c_str());
+            }
+        }
+    }
+
+    END_TEST;
+}
+
 // Make sure that we can parse boot args from a configuration string
 bool TestParseBootArgs() {
     BEGIN_TEST;
@@ -92,8 +163,8 @@ key=value
     END_TEST;
 }
 
-// Make sure the Arguments service works
-bool TestArguments() {
+// Make sure the fuchsia.boot.Arguments service works
+bool TestBootArguments() {
     BEGIN_TEST;
 
     zx::channel local, remote;
@@ -122,8 +193,8 @@ bool TestArguments() {
     END_TEST;
 }
 
-// Make sure the Items service works
-bool TestItems() {
+// Make sure the fuchsia.boot.Items service works
+bool TestBootItems() {
     BEGIN_TEST;
 
     zx::channel local, remote;
@@ -166,8 +237,8 @@ bool TestItems() {
     END_TEST;
 }
 
-// Make sure the RootResource service works
-bool TestRootResource() {
+// Make sure the fuchsia.boot.RootResource service works
+bool TestBootRootResource() {
     BEGIN_TEST;
 
     zx::channel local, remote;
@@ -219,9 +290,11 @@ bool TestVdsosPresent() {
 BEGIN_TEST_CASE(bootsvc_integration_tests)
 RUN_TEST(TestLoader)
 RUN_TEST(TestNamespace)
-RUN_TEST(TestParseBootArgs)
 RUN_TEST(TestArguments)
-RUN_TEST(TestItems)
-RUN_TEST(TestRootResource)
+RUN_TEST(TestParseBootArgs)
+RUN_TEST(TestSplitString)
+RUN_TEST(TestBootArguments)
+RUN_TEST(TestBootItems)
+RUN_TEST(TestBootRootResource)
 RUN_TEST(TestVdsosPresent)
 END_TEST_CASE(bootsvc_integration_tests)
diff --git a/zircon/system/core/bootsvc/main.cpp b/zircon/system/core/bootsvc/main.cpp
index 9a9765926923f5b05c3fad30bed35c8be60a1799..e681897ebfea2dd7df156d26dbe9fd1d965c67be 100644
--- a/zircon/system/core/bootsvc/main.cpp
+++ b/zircon/system/core/bootsvc/main.cpp
@@ -3,9 +3,12 @@
 // found in the LICENSE file.
 
 #include <ctype.h>
+#include <sstream>
 #include <stdio.h>
 #include <utility>
 
+#include <fbl/string.h>
+#include <fbl/vector.h>
 #include <fuchsia/boot/c/fidl.h>
 #include <launchpad/launchpad.h>
 #include <lib/async-loop/cpp/loop.h>
@@ -109,14 +112,20 @@ struct LaunchNextProcessArgs {
 int LaunchNextProcess(void* raw_ctx) {
     fbl::unique_ptr<LaunchNextProcessArgs> args(static_cast<LaunchNextProcessArgs*>(raw_ctx));
 
-    const char* next_program = getenv("bootsvc.next");
-    if (next_program == nullptr) {
-        next_program = "bin/devcoordinator";
+    const char* bootsvc_next = getenv("bootsvc.next");
+    if (bootsvc_next == nullptr) {
+        bootsvc_next = "bin/devcoordinator";
     }
 
+    // Split the bootsvc.next value into 1 or more arguments using ',' as a
+    // delimiter.
+    printf("bootsvc: bootsvc.next = %s\n", bootsvc_next);
+    fbl::Vector<fbl::String> next_args = bootsvc::SplitString(bootsvc_next, ',');
+
     // Open the executable we will start next
     zx::vmo program;
     uint64_t file_size;
+    const char* next_program = next_args[0].c_str();
     zx_status_t status = args->bootfs->Open(next_program, &program, &file_size);
     ZX_ASSERT_MSG(status == ZX_OK, "bootsvc: failed to open '%s': %s\n", next_program,
                   zx_status_get_string(status));
@@ -146,6 +155,13 @@ int LaunchNextProcess(void* raw_ctx) {
     launchpad_add_handle(lp, svcfs_conn.release(), PA_HND(PA_NS_DIR, count));
     nametable[count++] = "/bootsvc";
 
+    int argc = static_cast<int>(next_args.size());
+    const char* argv[argc];
+    for (int i = 0; i < argc; ++i) {
+        argv[i] = next_args[i].c_str();
+    }
+    launchpad_set_args(lp, argc, argv);
+
     ZX_ASSERT(count <= fbl::count_of(nametable));
     launchpad_set_nametable(lp, count, nametable);
 
diff --git a/zircon/system/core/bootsvc/util.cpp b/zircon/system/core/bootsvc/util.cpp
index e3d4a7506515b090a4b380bfee1a33d87b1b5f07..f3bfa282d76e3aa99b644754e8c295afa54c2abe 100644
--- a/zircon/system/core/bootsvc/util.cpp
+++ b/zircon/system/core/bootsvc/util.cpp
@@ -166,8 +166,8 @@ zx_status_t CreateVnodeConnection(fs::Vfs* vfs, fbl::RefPtr<fs::Vnode> vnode, zx
 
     auto conn = std::make_unique<fs::Connection>(vfs, vnode, std::move(local),
                                                  ZX_FS_FLAG_DIRECTORY |
-                                                 ZX_FS_RIGHT_READABLE |
-                                                 ZX_FS_RIGHT_WRITABLE);
+                                                     ZX_FS_RIGHT_READABLE |
+                                                     ZX_FS_RIGHT_WRITABLE);
     status = vfs->ServeConnection(std::move(conn));
     if (status != ZX_OK) {
         return status;
@@ -176,4 +176,19 @@ zx_status_t CreateVnodeConnection(fs::Vfs* vfs, fbl::RefPtr<fs::Vnode> vnode, zx
     return ZX_OK;
 }
 
+fbl::Vector<fbl::String> SplitString(fbl::String input, char delimiter) {
+    fbl::Vector<fbl::String> result;
+
+    // No fbl::String::find, do it ourselves.
+    const char* start = input.begin();
+    for (auto end = start; end != input.end(); start = end + 1) {
+        end = start;
+        while (end != input.end() && *end != delimiter) {
+            ++end;
+        }
+        result.push_back(fbl::String(start, end - start));
+    }
+    return result;
+}
+
 } // namespace bootsvc
diff --git a/zircon/system/core/bootsvc/util.h b/zircon/system/core/bootsvc/util.h
index 91c3e04ff46b75ecebdf49cb83be13bcbf5ada2f..b0a70a73492e59418c7f244454f87c785f92f8e9 100644
--- a/zircon/system/core/bootsvc/util.h
+++ b/zircon/system/core/bootsvc/util.h
@@ -7,6 +7,7 @@
 #include <map>
 #include <string_view>
 
+#include <fbl/string.h>
 #include <fbl/vector.h>
 #include <fs/vfs.h>
 #include <lib/zx/channel.h>
@@ -49,4 +50,6 @@ zx_status_t CreateVnodeConnection(fs::Vfs* vfs, fbl::RefPtr<fs::Vnode> vnode, zx
 // Path relative to /boot used for crashlogs.
 extern const char* const kLastPanicFilePath;
 
+fbl::Vector<fbl::String> SplitString(fbl::String input, char delimiter);
+
 } // namespace bootsvc