This repository has been archived by the owner on Jan 27, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
ConfuserEngine.cs
538 lines (462 loc) · 21.5 KB
/
ConfuserEngine.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Confuser.Core.Services;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
using dnlib.DotNet.Writer;
using Microsoft.Win32;
using InformationalAttribute = System.Reflection.AssemblyInformationalVersionAttribute;
using ProductAttribute = System.Reflection.AssemblyProductAttribute;
using CopyrightAttribute = System.Reflection.AssemblyCopyrightAttribute;
using MethodAttributes = dnlib.DotNet.MethodAttributes;
using MethodImplAttributes = dnlib.DotNet.MethodImplAttributes;
using TypeAttributes = dnlib.DotNet.TypeAttributes;
namespace Confuser.Core {
/// <summary>
/// The processing engine of ConfuserEx.
/// </summary>
public static class ConfuserEngine {
/// <summary>
/// The version of ConfuserEx.
/// </summary>
public static readonly string Version;
static readonly string Copyright;
static ConfuserEngine() {
Assembly assembly = typeof(ConfuserEngine).Assembly;
var nameAttr = (ProductAttribute)assembly.GetCustomAttributes(typeof(ProductAttribute), false)[0];
var verAttr = (InformationalAttribute)assembly.GetCustomAttributes(typeof(InformationalAttribute), false)[0];
var cpAttr = (CopyrightAttribute)assembly.GetCustomAttributes(typeof(CopyrightAttribute), false)[0];
Version = string.Format("{0} {1}", nameAttr.Product, verAttr.InformationalVersion);
Copyright = cpAttr.Copyright;
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => {
try {
var asmName = new AssemblyName(e.Name);
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
if (asm.GetName().Name == asmName.Name)
return asm;
return null;
}
catch {
return null;
}
};
}
/// <summary>
/// Runs the engine with the specified parameters.
/// </summary>
/// <param name="parameters">The parameters.</param>
/// <param name="token">The token used for cancellation.</param>
/// <returns>Task to run the engine.</returns>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="parameters" />.Project is <c>null</c>.
/// </exception>
public static Task Run(ConfuserParameters parameters, CancellationToken? token = null) {
if (parameters.Project == null)
throw new ArgumentNullException("parameters");
if (token == null)
token = new CancellationTokenSource().Token;
return Task.Factory.StartNew(() => RunInternal(parameters, token.Value), token.Value);
}
/// <summary>
/// Runs the engine.
/// </summary>
/// <param name="parameters">The parameters.</param>
/// <param name="token">The cancellation token.</param>
static void RunInternal(ConfuserParameters parameters, CancellationToken token) {
// 1. Setup context
var context = new ConfuserContext();
context.Logger = parameters.GetLogger();
context.Project = parameters.Project.Clone();
context.PackerInitiated = parameters.PackerInitiated;
context.token = token;
PrintInfo(context);
bool ok = false;
try {
var asmResolver = new AssemblyResolver();
asmResolver.EnableTypeDefCache = true;
asmResolver.DefaultModuleContext = new ModuleContext(asmResolver);
context.Resolver = asmResolver;
context.BaseDirectory = Path.Combine(Environment.CurrentDirectory, parameters.Project.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar);
context.OutputDirectory = Path.Combine(parameters.Project.BaseDirectory, parameters.Project.OutputDirectory.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar);
foreach (string probePath in parameters.Project.ProbePaths)
asmResolver.PostSearchPaths.Insert(0, Path.Combine(context.BaseDirectory, probePath));
context.CheckCancellation();
Marker marker = parameters.GetMarker();
// 2. Discover plugins
context.Logger.Debug("Discovering plugins...");
IList<Protection> prots;
IList<Packer> packers;
IList<ConfuserComponent> components;
parameters.GetPluginDiscovery().GetPlugins(context, out prots, out packers, out components);
context.Logger.InfoFormat("Discovered {0} protections, {1} packers.", prots.Count, packers.Count);
context.CheckCancellation();
// 3. Resolve dependency
context.Logger.Debug("Resolving component dependency...");
try {
var resolver = new DependencyResolver(prots);
prots = resolver.SortDependency();
}
catch (CircularDependencyException ex) {
context.Logger.ErrorException("", ex);
throw new ConfuserException(ex);
}
components.Insert(0, new CoreComponent(parameters, marker));
foreach (Protection prot in prots)
components.Add(prot);
foreach (Packer packer in packers)
components.Add(packer);
context.CheckCancellation();
// 4. Load modules
context.Logger.Info("Loading input modules...");
marker.Initalize(prots, packers);
MarkerResult markings = marker.MarkProject(parameters.Project, context);
context.Modules = new ModuleSorter(markings.Modules).Sort().ToList().AsReadOnly();
foreach (var module in context.Modules)
module.EnableTypeDefFindCache = false;
context.OutputModules = Enumerable.Repeat<byte[]>(null, context.Modules.Count).ToArray();
context.OutputSymbols = Enumerable.Repeat<byte[]>(null, context.Modules.Count).ToArray();
context.OutputPaths = Enumerable.Repeat<string>(null, context.Modules.Count).ToArray();
context.Packer = markings.Packer;
context.ExternalModules = markings.ExternalModules;
context.CheckCancellation();
// 5. Initialize components
context.Logger.Info("Initializing...");
foreach (ConfuserComponent comp in components) {
try {
comp.Initialize(context);
}
catch (Exception ex) {
context.Logger.ErrorException("Error occured during initialization of '" + comp.Name + "'.", ex);
throw new ConfuserException(ex);
}
context.CheckCancellation();
}
context.CheckCancellation();
// 6. Build pipeline
context.Logger.Debug("Building pipeline...");
var pipeline = new ProtectionPipeline();
context.Pipeline = pipeline;
foreach (ConfuserComponent comp in components) {
comp.PopulatePipeline(pipeline);
}
context.CheckCancellation();
//7. Run pipeline
RunPipeline(pipeline, context);
ok = true;
}
catch (AssemblyResolveException ex) {
context.Logger.ErrorException("Failed to resolve an assembly, check if all dependencies are present in the correct version.", ex);
PrintEnvironmentInfo(context);
}
catch (TypeResolveException ex) {
context.Logger.ErrorException("Failed to resolve a type, check if all dependencies are present in the correct version.", ex);
PrintEnvironmentInfo(context);
}
catch (MemberRefResolveException ex) {
context.Logger.ErrorException("Failed to resolve a member, check if all dependencies are present in the correct version.", ex);
PrintEnvironmentInfo(context);
}
catch (IOException ex) {
context.Logger.ErrorException("An IO error occurred, check if all input/output locations are readable/writable.", ex);
}
catch (OperationCanceledException) {
context.Logger.Error("Operation cancelled.");
}
catch (ConfuserException) {
// Exception is already handled/logged, so just ignore and report failure
}
catch (Exception ex) {
context.Logger.ErrorException("Unknown error occurred.", ex);
}
finally {
if (context.Resolver != null)
context.Resolver.Clear();
context.Logger.Finish(ok);
}
}
/// <summary>
/// Runs the protection pipeline.
/// </summary>
/// <param name="pipeline">The protection pipeline.</param>
/// <param name="context">The context.</param>
static void RunPipeline(ProtectionPipeline pipeline, ConfuserContext context) {
Func<IList<IDnlibDef>> getAllDefs = () => context.Modules.SelectMany(module => module.FindDefinitions()).ToList();
Func<ModuleDef, IList<IDnlibDef>> getModuleDefs = module => module.FindDefinitions().ToList();
context.CurrentModuleIndex = -1;
pipeline.ExecuteStage(PipelineStage.Inspection, Inspection, () => getAllDefs(), context);
var options = new ModuleWriterOptionsBase[context.Modules.Count];
var listeners = new ModuleWriterListener[context.Modules.Count];
for (int i = 0; i < context.Modules.Count; i++) {
context.CurrentModuleIndex = i;
context.CurrentModuleWriterOptions = null;
context.CurrentModuleWriterListener = null;
pipeline.ExecuteStage(PipelineStage.BeginModule, BeginModule, () => getModuleDefs(context.CurrentModule), context);
pipeline.ExecuteStage(PipelineStage.ProcessModule, ProcessModule, () => getModuleDefs(context.CurrentModule), context);
pipeline.ExecuteStage(PipelineStage.OptimizeMethods, OptimizeMethods, () => getModuleDefs(context.CurrentModule), context);
pipeline.ExecuteStage(PipelineStage.EndModule, EndModule, () => getModuleDefs(context.CurrentModule), context);
options[i] = context.CurrentModuleWriterOptions;
listeners[i] = context.CurrentModuleWriterListener;
}
for (int i = 0; i < context.Modules.Count; i++) {
context.CurrentModuleIndex = i;
context.CurrentModuleWriterOptions = options[i];
context.CurrentModuleWriterListener = listeners[i];
pipeline.ExecuteStage(PipelineStage.WriteModule, WriteModule, () => getModuleDefs(context.CurrentModule), context);
context.OutputModules[i] = context.CurrentModuleOutput;
context.OutputSymbols[i] = context.CurrentModuleSymbol;
context.CurrentModuleWriterOptions = null;
context.CurrentModuleWriterListener = null;
context.CurrentModuleOutput = null;
context.CurrentModuleSymbol = null;
}
context.CurrentModuleIndex = -1;
pipeline.ExecuteStage(PipelineStage.Debug, Debug, () => getAllDefs(), context);
pipeline.ExecuteStage(PipelineStage.Pack, Pack, () => getAllDefs(), context);
pipeline.ExecuteStage(PipelineStage.SaveModules, SaveModules, () => getAllDefs(), context);
if (!context.PackerInitiated)
context.Logger.Info("Done.");
}
static void Inspection(ConfuserContext context) {
context.Logger.Info("Resolving dependencies...");
foreach (var dependency in context.Modules
.SelectMany(module => module.GetAssemblyRefs().Select(asmRef => Tuple.Create(asmRef, module)))) {
try {
AssemblyDef assembly = context.Resolver.ResolveThrow(dependency.Item1, dependency.Item2);
}
catch (AssemblyResolveException ex) {
context.Logger.ErrorException("Failed to resolve dependency of '" + dependency.Item2.Name + "'.", ex);
throw new ConfuserException(ex);
}
}
context.Logger.Debug("Checking Strong Name...");
foreach (ModuleDefMD module in context.Modules) {
var snKey = context.Annotations.Get<StrongNameKey>(module, Marker.SNKey);
if (snKey == null && module.IsStrongNameSigned)
context.Logger.WarnFormat("[{0}] SN Key is not provided for a signed module, the output may not be working.", module.Name);
else if (snKey != null && !module.IsStrongNameSigned)
context.Logger.WarnFormat("[{0}] SN Key is provided for an unsigned module, the output may not be working.", module.Name);
else if (snKey != null && module.IsStrongNameSigned &&
!module.Assembly.PublicKey.Data.SequenceEqual(snKey.PublicKey))
context.Logger.WarnFormat("[{0}] Provided SN Key and signed module's public key do not match, the output may not be working.", module.Name);
}
var marker = context.Registry.GetService<IMarkerService>();
context.Logger.Debug("Creating global .cctors...");
foreach (ModuleDefMD module in context.Modules) {
TypeDef modType = module.GlobalType;
if (modType == null) {
modType = new TypeDefUser("", "<Module>", null);
modType.Attributes = TypeAttributes.AnsiClass;
module.Types.Add(modType);
marker.Mark(modType, null);
}
MethodDef cctor = modType.FindOrCreateStaticConstructor();
if (!marker.IsMarked(cctor))
marker.Mark(cctor, null);
}
context.Logger.Debug("Watermarking...");
foreach (ModuleDefMD module in context.Modules) {
TypeRef attrRef = module.CorLibTypes.GetTypeRef("System", "Attribute");
var attrType = new TypeDefUser("", "ConfusedByAttribute", attrRef);
module.Types.Add(attrType);
marker.Mark(attrType, null);
var ctor = new MethodDefUser(
".ctor",
MethodSig.CreateInstance(module.CorLibTypes.Void, module.CorLibTypes.String),
MethodImplAttributes.Managed,
MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
ctor.Body = new CilBody();
ctor.Body.MaxStack = 1;
ctor.Body.Instructions.Add(OpCodes.Ldarg_0.ToInstruction());
ctor.Body.Instructions.Add(OpCodes.Call.ToInstruction(new MemberRefUser(module, ".ctor", MethodSig.CreateInstance(module.CorLibTypes.Void), attrRef)));
ctor.Body.Instructions.Add(OpCodes.Ret.ToInstruction());
attrType.Methods.Add(ctor);
marker.Mark(ctor, null);
var attr = new CustomAttribute(ctor);
attr.ConstructorArguments.Add(new CAArgument(module.CorLibTypes.String, Version));
module.CustomAttributes.Add(attr);
}
}
static void CopyPEHeaders(PEHeadersOptions writerOptions, ModuleDefMD module) {
var image = module.MetaData.PEImage;
writerOptions.MajorImageVersion = image.ImageNTHeaders.OptionalHeader.MajorImageVersion;
writerOptions.MajorLinkerVersion = image.ImageNTHeaders.OptionalHeader.MajorLinkerVersion;
writerOptions.MajorOperatingSystemVersion = image.ImageNTHeaders.OptionalHeader.MajorOperatingSystemVersion;
writerOptions.MajorSubsystemVersion = image.ImageNTHeaders.OptionalHeader.MajorSubsystemVersion;
writerOptions.MinorImageVersion = image.ImageNTHeaders.OptionalHeader.MinorImageVersion;
writerOptions.MinorLinkerVersion = image.ImageNTHeaders.OptionalHeader.MinorLinkerVersion;
writerOptions.MinorOperatingSystemVersion = image.ImageNTHeaders.OptionalHeader.MinorOperatingSystemVersion;
writerOptions.MinorSubsystemVersion = image.ImageNTHeaders.OptionalHeader.MinorSubsystemVersion;
}
static void BeginModule(ConfuserContext context) {
context.Logger.InfoFormat("Processing module '{0}'...", context.CurrentModule.Name);
context.CurrentModuleWriterListener = new ModuleWriterListener();
context.CurrentModuleWriterListener.OnWriterEvent += (sender, e) => context.CheckCancellation();
context.CurrentModuleWriterOptions = new ModuleWriterOptions(context.CurrentModule, context.CurrentModuleWriterListener);
CopyPEHeaders(context.CurrentModuleWriterOptions.PEHeadersOptions, context.CurrentModule);
if (!context.CurrentModule.IsILOnly || context.CurrentModule.VTableFixups != null)
context.RequestNative();
var snKey = context.Annotations.Get<StrongNameKey>(context.CurrentModule, Marker.SNKey);
context.CurrentModuleWriterOptions.InitializeStrongNameSigning(context.CurrentModule, snKey);
foreach (TypeDef type in context.CurrentModule.GetTypes())
foreach (MethodDef method in type.Methods) {
if (method.Body != null) {
method.Body.Instructions.SimplifyMacros(method.Body.Variables, method.Parameters);
}
}
}
static void ProcessModule(ConfuserContext context) { }
static void OptimizeMethods(ConfuserContext context) {
foreach (TypeDef type in context.CurrentModule.GetTypes())
foreach (MethodDef method in type.Methods) {
if (method.Body != null)
method.Body.Instructions.OptimizeMacros();
}
}
static void EndModule(ConfuserContext context) {
string output = context.Modules[context.CurrentModuleIndex].Location;
if (output != null) {
if (!Path.IsPathRooted(output))
output = Path.Combine(Environment.CurrentDirectory, output);
output = Utils.GetRelativePath(output, context.BaseDirectory);
}
else {
output = context.CurrentModule.Name;
}
context.OutputPaths[context.CurrentModuleIndex] = output;
}
static void WriteModule(ConfuserContext context) {
context.Logger.InfoFormat("Writing module '{0}'...", context.CurrentModule.Name);
MemoryStream pdb = null, output = new MemoryStream();
if (context.CurrentModule.PdbState != null) {
pdb = new MemoryStream();
context.CurrentModuleWriterOptions.WritePdb = true;
context.CurrentModuleWriterOptions.PdbFileName = Path.ChangeExtension(Path.GetFileName(context.OutputPaths[context.CurrentModuleIndex]), "pdb");
context.CurrentModuleWriterOptions.PdbStream = pdb;
}
if (context.CurrentModuleWriterOptions is ModuleWriterOptions)
context.CurrentModule.Write(output, (ModuleWriterOptions)context.CurrentModuleWriterOptions);
else
context.CurrentModule.NativeWrite(output, (NativeModuleWriterOptions)context.CurrentModuleWriterOptions);
context.CurrentModuleOutput = output.ToArray();
if (context.CurrentModule.PdbState != null)
context.CurrentModuleSymbol = pdb.ToArray();
}
static void Debug(ConfuserContext context) {
context.Logger.Info("Finalizing...");
for (int i = 0; i < context.OutputModules.Count; i++) {
if (context.OutputSymbols[i] == null)
continue;
string path = Path.GetFullPath(Path.Combine(context.OutputDirectory, context.OutputPaths[i]));
string dir = Path.GetDirectoryName(path);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
File.WriteAllBytes(Path.ChangeExtension(path, "pdb"), context.OutputSymbols[i]);
}
}
static void Pack(ConfuserContext context) {
if (context.Packer != null) {
context.Logger.Info("Packing...");
context.Packer.Pack(context, new ProtectionParameters(context.Packer, context.Modules.OfType<IDnlibDef>().ToList()));
}
}
static void SaveModules(ConfuserContext context) {
context.Resolver.Clear();
for (int i = 0; i < context.OutputModules.Count; i++) {
string path = Path.GetFullPath(Path.Combine(context.OutputDirectory, context.OutputPaths[i]));
string dir = Path.GetDirectoryName(path);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
context.Logger.DebugFormat("Saving to '{0}'...", path);
File.WriteAllBytes(path, context.OutputModules[i]);
}
}
/// <summary>
/// Prints the copyright stuff and environment information.
/// </summary>
/// <param name="context">The working context.</param>
static void PrintInfo(ConfuserContext context) {
if (context.PackerInitiated) {
context.Logger.Info("Protecting packer stub...");
}
else {
context.Logger.InfoFormat("{0} {1}", Version, Copyright);
Type mono = Type.GetType("Mono.Runtime");
context.Logger.InfoFormat("Running on {0}, {1}, {2} bits",
Environment.OSVersion,
mono == null ?
".NET Framework v" + Environment.Version :
mono.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, null),
IntPtr.Size * 8);
}
}
static IEnumerable<string> GetFrameworkVersions() {
// http://msdn.microsoft.com/en-us/library/hh925568.aspx
using (RegistryKey ndpKey =
RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, "").
OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\")) {
foreach (string versionKeyName in ndpKey.GetSubKeyNames()) {
if (!versionKeyName.StartsWith("v"))
continue;
RegistryKey versionKey = ndpKey.OpenSubKey(versionKeyName);
var name = (string)versionKey.GetValue("Version", "");
string sp = versionKey.GetValue("SP", "").ToString();
string install = versionKey.GetValue("Install", "").ToString();
if (install == "" || sp != "" && install == "1")
yield return versionKeyName + " " + name;
if (name != "")
continue;
foreach (string subKeyName in versionKey.GetSubKeyNames()) {
RegistryKey subKey = versionKey.OpenSubKey(subKeyName);
name = (string)subKey.GetValue("Version", "");
if (name != "")
sp = subKey.GetValue("SP", "").ToString();
install = subKey.GetValue("Install", "").ToString();
if (install == "")
yield return versionKeyName + " " + name;
else if (install == "1")
yield return " " + subKeyName + " " + name;
}
}
}
using (RegistryKey ndpKey =
RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, "").
OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\")) {
if (ndpKey.GetValue("Release") == null)
yield break;
var releaseKey = (int)ndpKey.GetValue("Release");
yield return "v4.5 " + releaseKey;
}
}
/// <summary>
/// Prints the environment information when error occurred.
/// </summary>
/// <param name="context">The working context.</param>
static void PrintEnvironmentInfo(ConfuserContext context) {
if (context.PackerInitiated)
return;
context.Logger.Error("---BEGIN DEBUG INFO---");
context.Logger.Error("Installed Framework Versions:");
foreach (string ver in GetFrameworkVersions()) {
context.Logger.ErrorFormat(" {0}", ver.Trim());
}
context.Logger.Error("");
if (context.Resolver != null) {
context.Logger.Error("Cached assemblies:");
foreach (AssemblyDef asm in context.Resolver.GetCachedAssemblies()) {
if (string.IsNullOrEmpty(asm.ManifestModule.Location))
context.Logger.ErrorFormat(" {0}", asm.FullName);
else
context.Logger.ErrorFormat(" {0} ({1})", asm.FullName, asm.ManifestModule.Location);
foreach (var reference in asm.Modules.OfType<ModuleDefMD>().SelectMany(m => m.GetAssemblyRefs()))
context.Logger.ErrorFormat(" {0}", reference.FullName);
}
}
context.Logger.Error("---END DEBUG INFO---");
}
}
}