diff --git a/mps/code/addrobj.c b/mps/code/addrobj.c new file mode 100644 index 00000000000..f4fe524771a --- /dev/null +++ b/mps/code/addrobj.c @@ -0,0 +1,240 @@ +/* addrobj.c: BASE ADDRESS FROM INTERIOR POINTER TEST + * + * Copyright (c) 2023 Ravenbrook Limited. See end of file for license. + * + * .overview This test is for mps_addr_object(). Its intention is to + * verify that the function returns the appropriate base pointer to an + * object when provided with an interior pointer. It also tests that the + * function fails appropriately when the provided with a pointer to + * unmanaged memory, or to an object in a pool that doesn't support this + * feature. + * + * .limitations Objects that have been moved should cause the function to + * fail with MPS_RES_FAIL, however this is not tested. It could be tested if + * a testbench deliberately created a forwarding object, however this might + * confuse a pool that does automatic garbage collection such as AMC or AMCZ, + * so any such test would need to be designed to handle that. + * This test only examines behaviour in AMCZ and MVFF pools, i.e. A pool (AMCZ) + * which currently implements mps_addr_object() and one (MVFF) that doesn't. + */ + +#include "mps.h" +#include "testlib.h" +#include "fmtdy.h" +#include "fmtdytst.h" +#include "mpsavm.h" +#include "mpscamc.h" +#include "mpscmvff.h" +#include "stdio.h" +#include + +/* Define an object size to allocate. The size chosen doesn't matter much, except that this testbench assumes + that the object is large enough that a pointer could point to the interior of the object, without also + pointing to the base pointer of the object at the same time. For char pointers, this is probably 2 bytes. + Since we are using the Dylan library, we define the size of the object in terms of Dylan slots. See + fmtdytst.c for details of the Dylan object structure.*/ +#define N_SLOT_TESTOBJ 100 + +static void test_main(void) +{ + mps_arena_t arena; + mps_pool_t amcz_pool, mvff_pool; + mps_ap_t obj_ap; + mps_fmt_t obj_fmt; + mps_root_t testobj_root; + mps_res_t res; + /* In another testbench (extcon.c) we observed unreliable failures to do with registering the cold end + of the stack. See GitHub issue #210 + . For now, we + declare this as a separate root. */ + static mps_addr_t testobj; + mps_addr_t out, in; + + /* Create arena */ + die(mps_arena_create_k(&arena, mps_arena_class_vm(), mps_args_none), "mps_arena_create_k"); + + + /* INTRO TO TESTS: There are several tests. They test the expected "normal" operation of the + function, using an interior pointer, also corner cases where the interior pointer equals the + base pointer, where it equals the limit pointer. We also test asking about an address in unmanaged + memory, and about an address in a pool which currently does not support mps_addr_object. If you write + more tests, describe them here.*/ + + + /* TEST 1: Test using an interior pointer in an object in an AMCZ pool. + At the time of writing this test, the AMCZ pool is the only pool where + there exists a requirement to provide base addresses from interior pointers. + Currently, the AMCZ pool (and by extension, the AMC pool which shares the same + module as AMCZ) is the only pool for which mps_addr_object is implemented */ + + /* Use the dylan format for convenience */ + die(dylan_fmt(&obj_fmt, arena), "dylan_fmt"); + + /* Create the pool */ + MPS_ARGS_BEGIN(args) { + MPS_ARGS_ADD(args, MPS_KEY_FORMAT, obj_fmt); + die(mps_pool_create_k(&amcz_pool, arena, mps_class_amcz(), args), "mps_pool_create_k amcz"); + } MPS_ARGS_END(args); + + /* Create an area of ambiguous pointers to keep the object alive and in place, in this case + the area only contains room for a single reference since we are only using one object to test */ + die(mps_root_create_area(&testobj_root, arena, + mps_rank_ambig(), (mps_rm_t)0, + &testobj, &testobj+1, + mps_scan_area, NULL), + "mps_root_create_area"); + + /* Create the allocation point */ + die(mps_ap_create_k(&obj_ap, amcz_pool, mps_args_none), "mps_ap_create_k"); + + /* Make a Dylan object, size = (N_SLOT_TESTOBJ+2) * sizeof(mps_word_t). + (See fmtdytst.c for size calculation) */ + { + /* Because make_dylan_vector returns its pointer-to-object as an mps_word_t rather than an + mps_addr_t, and commits the object, we need to somehow safely allocate our object without + type punning and without risking that our object be destroyed. + Rather than redefine our reference table with type mps_word_t, which hides the intention of the table, + park the arena to disable garbage collection. Allocate our dylan object on the (unregistered) stack + storing its address in an mps_word_t. Then store this mps_word_t as an mps_addr_t in our reference + table, and release the arena since our object is now safely pinned. + Another approach would be to create another static registered root for ambiguous references of type + mps_word_t and then copy to the mps_addr_t root, which would avoid needing to park the arena. + */ + mps_word_t p_word; + mps_arena_park(arena); + die(make_dylan_vector(&p_word, obj_ap, N_SLOT_TESTOBJ), "make_dylan_vector"); + /* If we hadn't parked the arena, our vector might have been GC'd here */ + testobj = (mps_addr_t)p_word; + mps_arena_release(arena); + } + + /* Construct a pointer to roughly halfway inside the object */ + in = (mps_addr_t)((char *)testobj + (N_SLOT_TESTOBJ/2) * sizeof(mps_word_t)); + + /* Ensure that this is an interior pointer, and not the base pointer, + since we want to make sure we are testing with a true interior pointer and not + one that also happens to be the base pointer. This Insist is intended to protect + against the testbench losing its ability to test "true" interior pointers (i.e. ones + which don't match the base pointer) if the test object sizes were changed to be very + small. Note that we don't currently consider the "limit" of the object as a corner case + (so we don't Insist(in != limit) ) but we do consider limit+1, i.e. the pointer to the + next object to be a corner case. This test could be updated to consider in == limit as a + corner case. */ + Insist(in > testobj); + + /* Do Test */ + res = mps_addr_object(&out, arena, in); + Insist(out == testobj); + Insist(res == MPS_RES_OK); + printf("Interior pointer input: passed\n"); + + + /* TEST 2: Test using the base pointer itself as an input*/ + + in = testobj; + + /* Do Test */ + res = mps_addr_object(&out, arena, in); + Insist(out == testobj); + Insist(res == MPS_RES_OK); + printf("Base pointer input: passed\n"); + + + + /* TEST 3: Test using a pointer one-off-the-end of the object*/ + + in = (mps_addr_t)((char *)testobj + (N_SLOT_TESTOBJ + 2) * sizeof(mps_word_t)); + + /* Do Test */ + res = mps_addr_object(&out, arena, in); + Insist(res == MPS_RES_FAIL); + printf("Pointer to next object input: passed\n"); + + + /* Clean up from above tests */ + mps_root_destroy(testobj_root); + mps_ap_destroy(obj_ap); + mps_pool_destroy(amcz_pool); + mps_fmt_destroy(obj_fmt); + + + /* TEST 4: Test using a pointer in unmanaged memory */ + + /* Use malloc to allocate non-mps-managed memory on the heap */ + in = malloc(sizeof(mps_word_t)); + Insist(NULL != in); + + /* Do the test */ + res = mps_addr_object(&out, arena, in); + + /* Expect MPS to fail to find a base pointer for addresses not in managed memory */ + Insist(res == MPS_RES_FAIL); + printf("Pointer to unmanaged memory input: passed\n"); + + /* clean up from this test */ + if (NULL != in) + free(in); + + + /* TEST 5: Test using a pointer in a pool which currently doesn't implement mps_addr_object */ + + /* Create mvff pool for which mps_addr_object is not implemented */ + die(mps_pool_create_k(&mvff_pool, arena, mps_class_mvff(), mps_args_none), "mps_pool_create_k mvff"); + + /* allocate an object (just some memory) in this pool */ + die(mps_alloc(&in, mvff_pool, sizeof(mps_word_t)), "mps_alloc"); + + /* Do the test */ + res = mps_addr_object(&out, arena, in); + + Insist(res == MPS_RES_UNIMPL); + printf("Pointer to object in pool where mps_addr_object not implemented: passed\n"); + + + /* If more tests are added here, briefly describe them above under "INTRO TO TESTS" comment */ + + /* Final clean up */ + mps_free(mvff_pool, in, sizeof(mps_word_t)); + mps_pool_destroy(mvff_pool); + mps_arena_destroy(arena); +} + +int main(int argc, char *argv[]) +{ + testlib_init(argc, argv); + + test_main(); + + printf("%s: Conculsion, failed to find any defects.\n", argv[0]); + + return 0; +} + +/* C. COPYRIGHT AND LICENSE + * + * Copyright (C) 2022-2023 Ravenbrook Limited . + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/mps/code/arena.c b/mps/code/arena.c index 32de9f0aec1..7139ff4bc86 100644 --- a/mps/code/arena.c +++ b/mps/code/arena.c @@ -1364,6 +1364,22 @@ Bool ArenaHasAddr(Arena arena, Addr addr) return TractOfAddr(&tract, arena, addr); } +/* ArenaAddrObject -- return base pointer of managed object */ +Res ArenaAddrObject(Addr *pReturn, Arena arena, Addr addr) +{ + Tract tract; + + AVER(pReturn != NULL); + AVERT(Arena, arena); + + if (!TractOfAddr(&tract, arena, addr)) { + /* address does not belong to the arena */ + return ResFAIL; + } + + return PoolAddrObject(pReturn, TractPool(tract), addr); +} + /* C. COPYRIGHT AND LICENSE * diff --git a/mps/code/comm.gmk b/mps/code/comm.gmk index 3a241d84ffe..b5ced1136c7 100644 --- a/mps/code/comm.gmk +++ b/mps/code/comm.gmk @@ -253,6 +253,7 @@ LIB_TARGETS=mps.a mpsplan.a TEST_TARGETS=\ abqtest \ + addrobj \ airtest \ amcss \ amcsshe \ @@ -446,6 +447,9 @@ ifdef VARIETY $(PFM)/$(VARIETY)/abqtest: $(PFM)/$(VARIETY)/abqtest.o \ $(TESTLIBOBJ) $(PFM)/$(VARIETY)/mps.a +$(PFM)/$(VARIETY)/addrobj: $(PFM)/$(VARIETY)/addrobj.o \ + $(FMTDYTSTOBJ) $(TESTLIBOBJ) $(PFM)/$(VARIETY)/mps.a + $(PFM)/$(VARIETY)/airtest: $(PFM)/$(VARIETY)/airtest.o \ $(FMTSCMOBJ) $(TESTLIBOBJ) $(PFM)/$(VARIETY)/mps.a diff --git a/mps/code/commpost.nmk b/mps/code/commpost.nmk index a94c87b17ea..94152ca28db 100644 --- a/mps/code/commpost.nmk +++ b/mps/code/commpost.nmk @@ -177,6 +177,9 @@ $(PFM)\cool\mps.lib: $(MPMOBJ) $(PFM)\$(VARIETY)\abqtest.exe: $(PFM)\$(VARIETY)\abqtest.obj \ $(PFM)\$(VARIETY)\mps.lib $(TESTLIBOBJ) +$(PFM)\$(VARIETY)\addrobj.exe: $(PFM)\$(VARIETY)\addrobj.obj \ + $(PFM)\$(VARIETY)\mps.lib $(FMTTESTOBJ) $(TESTLIBOBJ) + $(PFM)\$(VARIETY)\airtest.exe: $(PFM)\$(VARIETY)\airtest.obj \ $(PFM)\$(VARIETY)\mps.lib $(FMTSCHEMEOBJ) $(TESTLIBOBJ) diff --git a/mps/code/commpre.nmk b/mps/code/commpre.nmk index 4a41ea56419..b485d25d44b 100644 --- a/mps/code/commpre.nmk +++ b/mps/code/commpre.nmk @@ -59,6 +59,7 @@ LIB_TARGETS=mps.lib TEST_TARGETS=\ abqtest.exe \ + addrobj.exe \ airtest.exe \ amcss.exe \ amcsshe.exe \ diff --git a/mps/code/mpm.h b/mps/code/mpm.h index 9979d20180d..e072a7a1ea7 100644 --- a/mps/code/mpm.h +++ b/mps/code/mpm.h @@ -236,6 +236,7 @@ extern Res PoolTraceBegin(Pool pool, Trace trace); extern void PoolFreeWalk(Pool pool, FreeBlockVisitor f, void *p); extern Size PoolTotalSize(Pool pool); extern Size PoolFreeSize(Pool pool); +extern Res PoolAddrObject(Addr *pReturn, Pool pool, Addr addr); extern Res PoolAbsInit(Pool pool, Arena arena, PoolClass klass, ArgList arg); extern void PoolAbsFinish(Inst inst); @@ -267,6 +268,7 @@ extern void PoolTrivFreeWalk(Pool pool, FreeBlockVisitor f, void *p); extern PoolDebugMixin PoolNoDebugMixin(Pool pool); extern BufferClass PoolNoBufferClass(void); extern Size PoolNoSize(Pool pool); +extern Res PoolTrivAddrObject(Addr *pReturn, Pool pool, Addr addr); /* See .critical.macros. */ #define PoolFreeMacro(pool, old, size) Method(Pool, pool, free)(pool, old, size) @@ -536,6 +538,7 @@ extern Res ArenaStartCollect(Globals globals, TraceStartWhy why); extern Res ArenaCollect(Globals globals, TraceStartWhy why); extern Bool ArenaBusy(Arena arena); extern Bool ArenaHasAddr(Arena arena, Addr addr); +extern Res ArenaAddrObject(Addr *pReturn, Arena arena, Addr addr); extern void ArenaChunkInsert(Arena arena, Chunk chunk); extern void ArenaChunkRemoved(Arena arena, Chunk chunk); extern void ArenaAccumulateTime(Arena arena, Clock start, Clock now); diff --git a/mps/code/mpmst.h b/mps/code/mpmst.h index e03033ae999..f5ba00b8d64 100644 --- a/mps/code/mpmst.h +++ b/mps/code/mpmst.h @@ -60,6 +60,7 @@ typedef struct mps_pool_class_s { PoolRampEndMethod rampEnd; /* end a ramp pattern */ PoolFramePushMethod framePush; /* push an allocation frame */ PoolFramePopMethod framePop; /* pop an allocation frame */ + PoolAddrObjectMethod addrObject; /* return object's base pointer */ PoolFreeWalkMethod freewalk; /* walk over free blocks */ PoolBufferClassMethod bufferClass; /* default BufferClass of pool */ PoolDebugMixinMethod debugMixin; /* find the debug mixin, if any */ diff --git a/mps/code/mpmtypes.h b/mps/code/mpmtypes.h index d913c445f76..c219e908e06 100644 --- a/mps/code/mpmtypes.h +++ b/mps/code/mpmtypes.h @@ -214,6 +214,7 @@ typedef Res (*PoolFramePushMethod)(AllocFrame *frameReturn, Pool pool, Buffer buf); typedef Res (*PoolFramePopMethod)(Pool pool, Buffer buf, AllocFrame frame); +typedef Res (*PoolAddrObjectMethod)(Addr *pReturn, Pool pool, Addr addr); typedef void (*PoolFreeWalkMethod)(Pool pool, FreeBlockVisitor f, void *p); typedef BufferClass (*PoolBufferClassMethod)(void); typedef PoolDebugMixin (*PoolDebugMixinMethod)(Pool pool); diff --git a/mps/code/mps.h b/mps/code/mps.h index 96214c89667..22709e4eca8 100644 --- a/mps/code/mps.h +++ b/mps/code/mps.h @@ -845,6 +845,8 @@ extern mps_res_t _mps_fix2(mps_ss_t, mps_addr_t *); (ss)->_ufs = _mps_ufs; \ MPS_END +/* Misc interface */ +extern mps_res_t mps_addr_object(mps_addr_t *p_o, mps_arena_t arena, mps_addr_t addr); #endif /* mps_h */ diff --git a/mps/code/mps.xcodeproj/project.pbxproj b/mps/code/mps.xcodeproj/project.pbxproj index 42f60ccb58a..909dbfbd601 100644 --- a/mps/code/mps.xcodeproj/project.pbxproj +++ b/mps/code/mps.xcodeproj/project.pbxproj @@ -73,6 +73,7 @@ buildPhases = ( ); dependencies = ( + 319F7A192A30D2F000E5B418 /* PBXTargetDependency */, 3104AFF6156D37BC000A585A /* PBXTargetDependency */, 3114A644156E94FB001E0AA3 /* PBXTargetDependency */, 22FACEF1188809B5000FDBC1 /* PBXTargetDependency */, @@ -89,8 +90,6 @@ 3114A677156E961C001E0AA3 /* PBXTargetDependency */, 3114A612156E943B001E0AA3 /* PBXTargetDependency */, 22B2BC3D18B643B300C33E63 /* PBXTargetDependency */, - 2291A5E6175CB207001D4920 /* PBXTargetDependency */, - 2291A5E8175CB20E001D4920 /* PBXTargetDependency */, 3114A5CC156E932C001E0AA3 /* PBXTargetDependency */, 3114A5EA156E93C4001E0AA3 /* PBXTargetDependency */, 22EA3F4820D2B23F0065F5B6 /* PBXTargetDependency */, @@ -158,16 +157,6 @@ 2291A5B5175CAB2F001D4920 /* testlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 31EEAC9E156AB73400714D05 /* testlib.c */; }; 2291A5B7175CAB2F001D4920 /* libmps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EEABFB156AAF9D00714D05 /* libmps.a */; }; 2291A5BE175CAB4E001D4920 /* awlutth.c in Sources */ = {isa = PBXBuildFile; fileRef = 2291A5A9175CAA9B001D4920 /* awlutth.c */; }; - 2291A5C5175CAFCA001D4920 /* fmtdy.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CAC6156BE48D00753214 /* fmtdy.c */; }; - 2291A5C6175CAFCA001D4920 /* fmtdytst.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CAC7156BE48D00753214 /* fmtdytst.c */; }; - 2291A5C7175CAFCA001D4920 /* fmtno.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CACC156BE4C200753214 /* fmtno.c */; }; - 2291A5C8175CAFCA001D4920 /* testlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 31EEAC9E156AB73400714D05 /* testlib.c */; }; - 2291A5CB175CAFCA001D4920 /* libmps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EEABFB156AAF9D00714D05 /* libmps.a */; }; - 2291A5D8175CB05F001D4920 /* fmtdy.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CAC6156BE48D00753214 /* fmtdy.c */; }; - 2291A5D9175CB05F001D4920 /* fmtdytst.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CAC7156BE48D00753214 /* fmtdytst.c */; }; - 2291A5DA175CB05F001D4920 /* fmtno.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CACC156BE4C200753214 /* fmtno.c */; }; - 2291A5DB175CB05F001D4920 /* testlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 31EEAC9E156AB73400714D05 /* testlib.c */; }; - 2291A5DD175CB05F001D4920 /* libmps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EEABFB156AAF9D00714D05 /* libmps.a */; }; 2291A5ED175CB5E2001D4920 /* landtest.c in Sources */ = {isa = PBXBuildFile; fileRef = 2291A5E9175CB4EC001D4920 /* landtest.c */; }; 22B2BC2E18B6434F00C33E63 /* mps.c in Sources */ = {isa = PBXBuildFile; fileRef = 31A47BA3156C1E130039B1C2 /* mps.c */; }; 22B2BC3718B6437C00C33E63 /* scheme-advanced.c in Sources */ = {isa = PBXBuildFile; fileRef = 22B2BC2B18B6434000C33E63 /* scheme-advanced.c */; }; @@ -301,6 +290,12 @@ 3124CAFC156BE82900753214 /* libmps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EEABFB156AAF9D00714D05 /* libmps.a */; }; 3150AE53156ABA2500A6E22A /* libmps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EEABFB156AAF9D00714D05 /* libmps.a */; }; 318DA8D31892B27E0089718C /* testlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 31EEAC9E156AB73400714D05 /* testlib.c */; }; + 319F7A082A30D08500E5B418 /* testlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 31EEAC9E156AB73400714D05 /* testlib.c */; }; + 319F7A0A2A30D08500E5B418 /* fmtdy.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CAC6156BE48D00753214 /* fmtdy.c */; }; + 319F7A0B2A30D08500E5B418 /* fmtdytst.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CAC7156BE48D00753214 /* fmtdytst.c */; }; + 319F7A0C2A30D08500E5B418 /* fmtno.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CACC156BE4C200753214 /* fmtno.c */; }; + 319F7A0E2A30D08500E5B418 /* libmps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EEABFB156AAF9D00714D05 /* libmps.a */; }; + 319F7A172A30D11400E5B418 /* addrobj.c in Sources */ = {isa = PBXBuildFile; fileRef = 319F7A152A30D11400E5B418 /* addrobj.c */; }; 31A47BA4156C1E130039B1C2 /* mps.c in Sources */ = {isa = PBXBuildFile; fileRef = 31A47BA3156C1E130039B1C2 /* mps.c */; }; 31D60007156D3C6200337B26 /* segsmss.c in Sources */ = {isa = PBXBuildFile; fileRef = 31D60006156D3C5F00337B26 /* segsmss.c */; }; 31D60008156D3C7400337B26 /* libmps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EEABFB156AAF9D00714D05 /* libmps.a */; }; @@ -468,20 +463,6 @@ remoteGlobalIDString = 2291A5AC175CAB2F001D4920; remoteInfo = awlutth; }; - 2291A5C3175CAFCA001D4920 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 31EEABDA156AAE9E00714D05 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 31EEABFA156AAF9D00714D05; - remoteInfo = mps; - }; - 2291A5D5175CB05F001D4920 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 31EEABDA156AAE9E00714D05 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 31EEABFA156AAF9D00714D05; - remoteInfo = mps; - }; 229E228719EAB10D00E21417 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 31EEABDA156AAE9E00714D05 /* Project object */; @@ -923,6 +904,20 @@ remoteGlobalIDString = 31108A3A1C6B90E900E728EA; remoteInfo = tagtest; }; + 319F7A062A30D08500E5B418 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31EEABDA156AAE9E00714D05 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 31EEABFA156AAF9D00714D05; + remoteInfo = mps; + }; + 319F7A182A30D2F000E5B418 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31EEABDA156AAE9E00714D05 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 319F7A042A30D08500E5B418; + remoteInfo = addrobj; + }; 31A47BA9156C210D0039B1C2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 31EEABDA156AAE9E00714D05 /* Project object */; @@ -1092,24 +1087,6 @@ ); runOnlyForDeploymentPostprocessing = 1; }; - 2291A5CC175CAFCA001D4920 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 1; - }; - 2291A5DE175CB05F001D4920 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 1; - }; 22B2BC3118B6434F00C33E63 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1407,6 +1384,15 @@ ); runOnlyForDeploymentPostprocessing = 1; }; + 319F7A0F2A30D08500E5B418 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; 31D6000B156D3CB200337B26 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1729,6 +1715,8 @@ 31942AA91C8EC446001AAF32 /* sp.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = sp.txt; path = ../design/sp.txt; sourceTree = ""; }; 31942AAB1C8EC446001AAF32 /* stack-scan.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = "stack-scan.txt"; path = "../design/stack-scan.txt"; sourceTree = ""; }; 31942AB01C8EC446001AAF32 /* testthr.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = testthr.txt; path = ../design/testthr.txt; sourceTree = ""; }; + 319F7A142A30D08500E5B418 /* addrobj */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = addrobj; sourceTree = BUILT_PRODUCTS_DIR; }; + 319F7A152A30D11400E5B418 /* addrobj.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = addrobj.c; sourceTree = ""; }; 31A47BA3156C1E130039B1C2 /* mps.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mps.c; sourceTree = ""; }; 31C83ADD1786281C0031A0DB /* protxc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = protxc.h; sourceTree = ""; }; 31CD33BB173A9F1500524741 /* mpscams.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mpscams.h; sourceTree = ""; }; @@ -1856,22 +1844,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 2291A5CA175CAFCA001D4920 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 2291A5CB175CAFCA001D4920 /* libmps.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 2291A5DC175CB05F001D4920 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 2291A5DD175CB05F001D4920 /* libmps.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 22B2BC3018B6434F00C33E63 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2134,6 +2106,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 319F7A0D2A30D08500E5B418 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 319F7A0E2A30D08500E5B418 /* libmps.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 31D6000A156D3CB200337B26 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2340,6 +2320,7 @@ 3124CAB3156BE1B700753214 /* Tests */ = { isa = PBXGroup; children = ( + 319F7A152A30D11400E5B418 /* addrobj.c */, 3114A63D156E94EA001E0AA3 /* abqtest.c */, 22FACED1188807FF000FDBC1 /* airtest.c */, 3124CAF5156BE81100753214 /* amcss.c */, @@ -2485,6 +2466,7 @@ 223E796519EAB00B00DC26A6 /* sncss */, 22EA3F4520D2B0D90065F5B6 /* forktest */, 2265D71D20E53F9C003019E8 /* mpseventpy */, + 319F7A142A30D08500E5B418 /* addrobj */, ); name = Products; sourceTree = ""; @@ -3365,6 +3347,24 @@ productReference = 318DA8CD1892B0F30089718C /* djbench */; productType = "com.apple.product-type.tool"; }; + 319F7A042A30D08500E5B418 /* addrobj */ = { + isa = PBXNativeTarget; + buildConfigurationList = 319F7A102A30D08500E5B418 /* Build configuration list for PBXNativeTarget "addrobj" */; + buildPhases = ( + 319F7A072A30D08500E5B418 /* Sources */, + 319F7A0D2A30D08500E5B418 /* Frameworks */, + 319F7A0F2A30D08500E5B418 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + 319F7A052A30D08500E5B418 /* PBXTargetDependency */, + ); + name = addrobj; + productName = finalcv; + productReference = 319F7A142A30D08500E5B418 /* addrobj */; + productType = "com.apple.product-type.tool"; + }; 31D6000C156D3CB200337B26 /* awluthe */ = { isa = PBXNativeTarget; buildConfigurationList = 31D60014156D3CB200337B26 /* Build configuration list for PBXNativeTarget "awluthe" */; @@ -3555,6 +3555,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = 31EEABD8156AAE9E00714D05; @@ -3617,6 +3618,7 @@ 31FCAE0917692403008C034C /* scheme */, 22B2BC2C18B6434F00C33E63 /* scheme-advanced */, 31108A3A1C6B90E900E728EA /* tagtest */, + 319F7A042A30D08500E5B418 /* addrobj */, ); }; /* End PBXProject section */ @@ -4090,6 +4092,18 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 319F7A072A30D08500E5B418 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 319F7A172A30D11400E5B418 /* addrobj.c in Sources */, + 319F7A082A30D08500E5B418 /* testlib.c in Sources */, + 319F7A0A2A30D08500E5B418 /* fmtdy.c in Sources */, + 319F7A0B2A30D08500E5B418 /* fmtdytst.c in Sources */, + 319F7A0C2A30D08500E5B418 /* fmtno.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 31D60009156D3CB200337B26 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -4287,16 +4301,6 @@ target = 2291A5AC175CAB2F001D4920 /* awlutth */; targetProxy = 2291A5BF175CAB5F001D4920 /* PBXContainerItemProxy */; }; - 2291A5C2175CAFCA001D4920 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 31EEABFA156AAF9D00714D05 /* mps */; - targetProxy = 2291A5C3175CAFCA001D4920 /* PBXContainerItemProxy */; - }; - 2291A5D4175CB05F001D4920 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 31EEABFA156AAF9D00714D05 /* mps */; - targetProxy = 2291A5D5175CB05F001D4920 /* PBXContainerItemProxy */; - }; 229E228819EAB10D00E21417 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 223E795819EAB00B00DC26A6 /* sncss */; @@ -4612,6 +4616,16 @@ target = 31108A3A1C6B90E900E728EA /* tagtest */; targetProxy = 314CB6EA1C6D272A0073CA42 /* PBXContainerItemProxy */; }; + 319F7A052A30D08500E5B418 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 31EEABFA156AAF9D00714D05 /* mps */; + targetProxy = 319F7A062A30D08500E5B418 /* PBXContainerItemProxy */; + }; + 319F7A192A30D2F000E5B418 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 319F7A042A30D08500E5B418 /* addrobj */; + targetProxy = 319F7A182A30D2F000E5B418 /* PBXContainerItemProxy */; + }; 31A47BAA156C210D0039B1C2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 31EEABFA156AAF9D00714D05 /* mps */; @@ -4891,34 +4905,6 @@ }; name = Release; }; - 2291A5CE175CAFCA001D4920 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 2291A5CF175CAFCA001D4920 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; - 2291A5E0175CB05F001D4920 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 2291A5E1175CB05F001D4920 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; 22B2BC3318B6434F00C33E63 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -5633,20 +5619,6 @@ }; name = RASH; }; - 318DA8E51892C0D00089718C /* RASH */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = RASH; - }; - 318DA8E61892C0D00089718C /* RASH */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = RASH; - }; 318DA8E71892C0D00089718C /* RASH */ = { isa = XCBuildConfiguration; buildSettings = { @@ -5816,6 +5788,27 @@ }; name = RASH; }; + 319F7A112A30D08500E5B418 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 319F7A122A30D08500E5B418 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 319F7A132A30D08500E5B418 /* RASH */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = RASH; + }; 31D60015156D3CB200337B26 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -6553,6 +6546,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 319F7A102A30D08500E5B418 /* Build configuration list for PBXNativeTarget "addrobj" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 319F7A112A30D08500E5B418 /* Debug */, + 319F7A122A30D08500E5B418 /* Release */, + 319F7A132A30D08500E5B418 /* RASH */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 31D60014156D3CB200337B26 /* Build configuration list for PBXNativeTarget "awluthe" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/mps/code/mps.xcodeproj/xcshareddata/xcschemes/addrobj.xcscheme b/mps/code/mps.xcodeproj/xcshareddata/xcschemes/addrobj.xcscheme new file mode 100644 index 00000000000..fc6990f251f --- /dev/null +++ b/mps/code/mps.xcodeproj/xcshareddata/xcschemes/addrobj.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mps/code/mps.xcodeproj/xcshareddata/xcschemes/finaltest.xcscheme b/mps/code/mps.xcodeproj/xcshareddata/xcschemes/finaltest.xcscheme new file mode 100644 index 00000000000..67ef98c4301 --- /dev/null +++ b/mps/code/mps.xcodeproj/xcshareddata/xcschemes/finaltest.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mps/code/mpsi.c b/mps/code/mpsi.c index 5f19333639e..a2fe1461590 100644 --- a/mps/code/mpsi.c +++ b/mps/code/mpsi.c @@ -448,6 +448,37 @@ mps_bool_t mps_addr_pool(mps_pool_t *mps_pool_o, } +/* mps_addr_object -- find base pointer of a managed object */ + +mps_res_t mps_addr_object(mps_addr_t *p_o, mps_arena_t arena, mps_addr_t addr) +{ + Res res; + Addr p; + + AVER(p_o != NULL); + + /* This function cannot be called while walking the heap, unlike + * mps_arena_has_addr(). This is because it is designed to be called + * with an active mutator, so takes the arena lock. This is in order + * that it sees a consistent view of MPS structures and the heap, + * and can peek behind the barrier. + */ + ArenaEnter(arena); + AVERT(Arena, arena); + res = ArenaAddrObject(&p, arena, (Addr)addr); + ArenaLeave(arena); + /* We require the object to be ambiguously referenced (hence pinned) + * so that p doesn't become invalid before it is written to *p_o. + * (We can't simply put this write before the ArenaLeave(), because + * p_o could point to MPS-managed memory that is behind a barrier.) + */ + if (res == ResOK) + *p_o = (mps_addr_t)p; + + return res; +} + + /* mps_addr_fmt -- what format might this address have? * * .per-pool: There's no reason why all objects in a pool should have diff --git a/mps/code/pool.c b/mps/code/pool.c index ea6971025e5..fc04563175b 100644 --- a/mps/code/pool.c +++ b/mps/code/pool.c @@ -57,6 +57,7 @@ Bool PoolClassCheck(PoolClass klass) CHECKL(FUNCHECK(klass->debugMixin)); CHECKL(FUNCHECK(klass->totalSize)); CHECKL(FUNCHECK(klass->freeSize)); + CHECKL(FUNCHECK(klass->addrObject)); /* Check that pool classes overide sets of related methods. */ CHECKL((klass->init == PoolAbsInit) == @@ -303,6 +304,21 @@ Size PoolFreeSize(Pool pool) } +/* PoolAddrObject -- return base pointer from interior pointer + * + * Note: addr is not necessarily inside the pool, even though + * mps_addr_object dispatches via the tract table. This allows this + * function to be used more generally internally. The pool should + * check (it has to anyway). + */ + +Res PoolAddrObject(Addr *pReturn, Pool pool, Addr addr) +{ + AVER(pReturn != NULL); + AVERT(Pool, pool); + return Method(Pool, pool, addrObject)(pReturn, pool, addr); +} + /* PoolDescribe -- describe a pool */ Res PoolDescribe(Pool pool, mps_lib_FILE *stream, Count depth) diff --git a/mps/code/poolabs.c b/mps/code/poolabs.c index f7fe65a8e41..d6eae2dd046 100644 --- a/mps/code/poolabs.c +++ b/mps/code/poolabs.c @@ -173,6 +173,7 @@ DEFINE_CLASS(Pool, AbstractPool, klass) klass->debugMixin = PoolNoDebugMixin; klass->totalSize = PoolNoSize; klass->freeSize = PoolNoSize; + klass->addrObject = PoolTrivAddrObject; klass->sig = PoolClassSig; AVERT(PoolClass, klass); } @@ -476,6 +477,16 @@ Size PoolNoSize(Pool pool) } +Res PoolTrivAddrObject(Addr *pReturn, Pool pool, Addr addr) +{ + AVERT(Pool, pool); + AVER(pReturn != NULL); + UNUSED(addr); + + return ResUNIMPL; +} + + /* C. COPYRIGHT AND LICENSE * * Copyright (C) 2001-2020 Ravenbrook Limited . diff --git a/mps/code/poolamc.c b/mps/code/poolamc.c index 8a29aee552d..1db13147fef 100644 --- a/mps/code/poolamc.c +++ b/mps/code/poolamc.c @@ -1893,6 +1893,90 @@ static void amcWalkAll(Pool pool, FormattedObjectsVisitor f, void *p, size_t s) } } +/* AMCAddrObject -- return base pointer from interior pointer + * + * amcAddrObjectSearch implements the scan for an object containing + * the interior pointer by skipping using format methods. + * + * AMCAddrObject locates the segment containing the interior pointer + * and wraps amcAddrObjectSearch in the necessary shield operations to + * give it access. + */ + +static Res amcAddrObjectSearch(Addr *pReturn, + Pool pool, + Addr objBase, + Addr searchLimit, + Addr addr) +{ + Format format; + Size hdrSize; + + AVER(pReturn != NULL); + AVERT(Pool, pool); + AVER(objBase <= searchLimit); + + format = pool->format; + hdrSize = format->headerSize; + while (objBase < searchLimit) { + Addr objRef = AddrAdd(objBase, hdrSize); + Addr objLimit = AddrSub((*format->skip)(objRef), hdrSize); + AVER(objBase < objLimit); + + if (addr < objLimit) { + AVER(objBase <= addr); + AVER(addr < objLimit); + + /* Don't return base pointer if object is moved */ + if (NULL == (*format->isMoved)(objRef)) { + *pReturn = objRef; + return ResOK; + } + break; + } + objBase = objLimit; + } + return ResFAIL; +} + +static Res AMCAddrObject(Addr *pReturn, Pool pool, Addr addr) +{ + Res res; + Arena arena; + Addr base, limit; + Buffer buffer; + Seg seg; + + AVER(pReturn != NULL); + AVERT(Pool, pool); + + arena = PoolArena(pool); + if (!SegOfAddr(&seg, arena, addr) || SegPool(seg) != pool) + return ResFAIL; + + base = SegBase(seg); + if (SegBuffer(&buffer, seg)) + /* We use BufferGetInit here (and not BufferScanLimit) because we + * want to be able to find objects that have been allocated and + * committed since the last flip. These objects lie between the + * addresses returned by BufferScanLimit (which returns the value + * of init at the last flip) and BufferGetInit. + * + * Strictly speaking we only need a limit that is at least the + * maximum of the objects on the segments. This is because addr + * *must* point inside a live object and we stop skipping once we + * have found it. The init pointer serves this purpose. + */ + limit = BufferGetInit(buffer); + else + limit = SegLimit(seg); + + ShieldExpose(arena, seg); + res = amcAddrObjectSearch(pReturn, pool, base, limit, addr); + ShieldCover(arena, seg); + return res; +} + /* AMCTotalSize -- total memory allocated from the arena */ @@ -2008,6 +2092,7 @@ DEFINE_CLASS(Pool, AMCZPool, klass) klass->bufferClass = amcBufClassGet; klass->totalSize = AMCTotalSize; klass->freeSize = AMCFreeSize; + klass->addrObject = AMCAddrObject; AVERT(PoolClass, klass); } diff --git a/mps/manual/source/release.rst b/mps/manual/source/release.rst index 6674cf691c9..91bf180247c 100644 --- a/mps/manual/source/release.rst +++ b/mps/manual/source/release.rst @@ -47,6 +47,12 @@ New features :ref:`topic-scanning-protocol`. This allows the client program to safely update references in the visited objects. +#. The new function :c:func:`mps_addr_object` allows clients to + discover the base pointer of an object from a pointer to anywhere + inside the object. This is intended to support stack tracing and + debugging for client programs that allocate their code on the + heap. + #. A :term:`virtual memory arena` can now be configured to call functions when it acquires a new chunk of :term:`address space`, and when it returns a chunk of address space to the operation diff --git a/mps/manual/source/topic/arena.rst b/mps/manual/source/topic/arena.rst index 5ca0dbbd0c8..61302d249a5 100644 --- a/mps/manual/source/topic/arena.rst +++ b/mps/manual/source/topic/arena.rst @@ -1054,6 +1054,52 @@ Arena introspection and debugging :c:func:`mps_addr_fmt`. +.. c:function:: mps_res_t mps_addr_object(mps_addr_t *p_o, mps_arena_t arena, mps_addr_t addr) + + Find the :term:`base pointer` of an :term:`object` if provided with an + :term:`interior pointer` to that object, or the object's base pointer, + provided the object exists in a pool that supports this feature. + + ``p_o`` points to a location that will hold the object's base pointer. + + ``arena`` is an arena. + + ``addr`` is an address that might be an interior or base pointer. + + Returns MPS_RES_OK if a base pointer to an object into which ``addr`` + points was successfully returned. + + Returns MPS_RES_FAIL if ``addr`` points to memory not managed by the + ``arena`` or if ``addr`` points to the interior of an object which has + been moved by a :term:`moving memory manager`. + + Returns MPS_RES_UNIMPL if ``addr`` is found to be managed by a :term:`pool` + which does not currently implement this feature. + + :c:func:`mps_addr_object` allows client programs that allocate + code on the heap to implement debugging and stack tracing, in that it provides + a way to unwind a client program's stack by finding the block of code to which the + program counter or function return addresses currently point. It can be called + multiple times as needed to build a complete trace of the client program's stack. + + This function does not support debugging in situations where the arena + itself has encountered a runtime error. For cases where the MPS encounters + runtime errors, see :c:func:`mps_arena_postmortem`. + + .. note:: + + This function is intended to assist with debugging fatal + errors in the :term:`client program`. It is not expected to be + needed in normal use, i.e. as part of the regular operation of code in + production, since it is not optimized for performance. If you find yourself + wanting to use this function other than in the use case described, there may + be a better way to meet your requirements: please + :ref:`contact us `. + + If you would like this function to work in a pool in which it's currently + unimplemented, please :ref:`contact us `. + + .. index:: single: arena extension callbacks; introduction single: extension callbacks; introduction diff --git a/mps/tool/testcases.txt b/mps/tool/testcases.txt index bf5c4e258c9..56787740ae2 100644 --- a/mps/tool/testcases.txt +++ b/mps/tool/testcases.txt @@ -2,6 +2,7 @@ Test case Flags Notes ============= ================ ========================================== abqtest +addrobj airtest amcss =P amcsshe =P